home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume27 / mthreads / part03 / mt-process.c
C/C++ Source or Header  |  1993-11-20  |  44KB  |  1,829 lines

  1. /* $Id: mt-process.c,v 3.0 1993/10/01 00:14:03 davison Trn $
  2. */
  3. /* The authors make no claims as to the fitness or correctness of this software
  4.  * for any use whatsoever, and it is provided as is. Any use of this software
  5.  * is at the user's own risk. 
  6.  */
  7.  
  8. #include "EXTERN.h"
  9. #include "common.h"
  10. #include "thread.h"
  11. #include "mthreads.h"
  12. #include "ndir.h"
  13. #include "nntpclient.h"
  14.  
  15. char references[1024];
  16.  
  17. char subject_str[80];
  18. bool found_Re;
  19.  
  20. char author_str[20];
  21.  
  22. ART_NUM absfirst;
  23. ART_NUM lastart;
  24. char *ctlarea;
  25.  
  26. extern int log_verbosity, slow_down;
  27.  
  28. long num;
  29.  
  30. DOMAIN *next_domain;
  31.  
  32. void insert_article(), expire(), trim_roots(), order_roots(), trim_authors();
  33. void make_root(), use_root(), merge_roots(), set_root(), unlink_root();
  34. void link_child(), unlink_child();
  35. void free_article(), free_domain(), free_subject(), free_root(), free_author();
  36. void get_subject_str(), get_author_str();
  37. int valid_message_id _((char *, char *));
  38. int subject_equal _((char *, char *));
  39. ARTICLE *get_article();
  40. SUBJECT *new_subject();
  41. AUTHOR *new_author();
  42.  
  43. #ifndef USE_NNTP
  44. static FILE *fp_article;
  45. #endif
  46.  
  47. /* Given the upper/lower bounds of the articles in the current group, add all
  48. ** the ones that we don't know about and remove all the ones that have expired.
  49. ** The current directory must be the newsgroup's spool directory.
  50. */
  51. void
  52. process_articles(first_article, last_article)
  53. ART_NUM first_article, last_article;
  54. {
  55.     register char *cp, *str;
  56.     register ARTICLE *article;
  57.     register ART_NUM i;
  58.     time_t date;
  59.     bool has_xrefs;
  60.     int len;
  61. #ifdef USE_NNTP
  62.     bool orig_extra = extra_expire;
  63. #else
  64.     extern int sys_nerr;
  65.     extern char *sys_errlist[];
  66. #endif
  67.     int start = total.last + 1;
  68.  
  69.     if (first_article > start) {
  70.     start = first_article;
  71.     }
  72.     added_count = last_article - start + 1;
  73.     if (added_count < 0) {
  74.     added_count = 0;
  75.     } else if (added_count > 1000) {
  76.     /* Don't overwork ourselves the first time */
  77.     added_count = 1000;
  78.     start = last_article - 1000 + 1;
  79.     }
  80.     expired_count = 0;
  81.  
  82.     for (i = start; i <= last_article; i++) {
  83. #ifdef USE_NNTP
  84.     if (slow_down) {
  85.         usleep(slow_down);
  86.     }
  87.     sprintf(ser_line, "HEAD %ld", (long)i);
  88.     nntp_command(ser_line);
  89.     if (nntp_check(FALSE) == NNTP_CLASS_FATAL) {
  90.         last_article = i - 1;
  91.         extra_expire = FALSE;
  92.         break;
  93.     }
  94.     if (*ser_line != NNTP_CLASS_OK) {
  95.         added_count--;
  96.         continue;
  97.     }
  98. #else
  99.     /* Open article in current directory. */
  100.     sprintf(buf, "%ld", (long)i);
  101.     /* Set errno for purely paranoid reasons */
  102.     errno = 0;
  103.     if ((fp_article = fopen(buf, "r")) == Nullfp) {
  104.         /* Missing files are ok -- they've just been expired or canceled */
  105.         if (errno != 0 && errno != ENOENT) {
  106.         if (errno < 0 || errno > sys_nerr) {
  107.             log_error("Can't open `%s': Error %d.\n", buf, errno);
  108.         } else {
  109.             log_error("Can't open `%s': %s.\n", buf,
  110.                   sys_errlist[errno]);
  111.         }
  112.         }
  113.         added_count--;
  114.         continue;
  115.     }
  116. #endif
  117.  
  118.     article = Nullart;
  119.     *references = '\0';
  120.     *author_str = '\0';
  121.     *subject_str = '\0';
  122.     found_Re = 0;
  123.     date = 0;
  124.     has_xrefs = FALSE;
  125.  
  126. #ifdef USE_NNTP
  127.     while (nntp_gets(cp = buf, sizeof buf) == 0) {
  128.       process_line:
  129.         if (*cp == '.') {
  130.         if (cp[1]) {
  131.             log_error("Header line starts with '.'! [%ld].\n",
  132.                 (long)i);
  133.             continue;
  134.         }
  135.         break;
  136.         }
  137. #else
  138.     while ((cp = fgets(buf, sizeof buf, fp_article)) != Nullch) {
  139.       process_line:
  140.         if (*cp == '\n') {        /* check for end of header */
  141.         break;            /* break out when found */
  142.         }
  143. #endif
  144.         if ((unsigned char)*cp <= ' ') {     /* skip continuation lines */
  145.         continue;        /* (except references -- see below) */
  146.         }
  147.         if ((str = index(cp, ':')) == Nullch) {
  148. #ifdef USE_NNTP
  149.         if (log_verbosity) {
  150.             log_error("Header line missing colon! [%ld].\n", (long)i);
  151.         }
  152.         continue;        /* skip bogus header line */
  153. #else
  154.         break;            /* end of header if no colon found */
  155. #endif
  156.         }
  157.         if ((len = str - cp) > 10) {
  158.         continue;        /* skip keywords > 10 chars */
  159.         }
  160. #ifndef USE_NNTP
  161.         cp[strlen(cp)-1] = '\0';    /* remove newline */
  162. #endif
  163.         while (cp < str) {        /* lower-case the keyword */
  164.         if ((unsigned char)*cp <= ' ') { /* stop at any whitespace */
  165.             break;
  166.         }
  167.         if (isupper(*cp)) {
  168.             *cp = tolower(*cp);
  169.         }
  170.         cp++;
  171.         }
  172.         *cp = '\0';
  173.         cp = buf;
  174.         if (len == 4 && strEQ(cp, "date")) {
  175.         date = parsedate(str + 1);
  176.         } else
  177.         if (len == 4 && strEQ(cp, "from")) {
  178.         get_author_str(str + 1);
  179.         } else
  180.         if (len == 4 && strEQ(cp, "xref")) {
  181.         has_xrefs = TRUE;
  182.         } else
  183.         if (len == 7 && strEQ(cp, "subject")) {
  184.         get_subject_str(str + 1);
  185.         } else
  186.         if (len == 10 && strEQ(cp, "message-id")) {
  187.         if (!article) {
  188.             article = get_article(str + 1);
  189.         } else {
  190.             if (log_verbosity) {
  191.             log_error("Found multiple Message-IDs! [%ld].\n",
  192.                 (long)i);
  193.             }
  194.         }
  195.         } else
  196.         if (len == 10 && strEQ(cp, "references")) {
  197.         /* include preceding space in saved reference */
  198.         len = strlen(str + 1);
  199.         bcopy(str + 1, references, len + 1);
  200.         str = references + len;
  201.         /* check for continuation lines */
  202. #ifdef USE_NNTP
  203.         while (nntp_gets(cp = buf, sizeof buf) == 0) {
  204. #else
  205.         while ((cp = fgets(buf, sizeof buf, fp_article)) != Nullch) {
  206. #endif
  207.             if (*cp != ' ' && *cp != '\t') {
  208.             goto process_line;
  209.             }
  210.             while (*++cp == ' ' || *cp == '\t') {
  211.             ;
  212.             }
  213.             *--cp = ' ';
  214.             /* If the references are too long, shift them over to
  215.             ** always save the most recent ones.
  216.             */
  217.             if ((len += strlen(cp)) > 1023) {
  218.             strcpy(buf, buf + len - 1023);
  219.             str -= len - 1023;
  220.             len = 1023;
  221.             }
  222.             strcpy(str, cp);
  223.         }/* while */
  224.         break;
  225.         }/* if */
  226.     }/* while */
  227.     if (article) {
  228.         num = i;
  229.         insert_article(article, date);
  230.         if (has_xrefs) {
  231.         article->flags |= HAS_XREFS;
  232.         }
  233.     } else {
  234.         if (log_verbosity) {
  235.         log_error("Message-ID line missing! [%ld].\n", (long)i);
  236.         }
  237.     }
  238. #ifndef USE_NNTP
  239.     fclose(fp_article);
  240. #endif
  241.     }
  242.  
  243.     if (extra_expire || first_article > total.first) {
  244.     absfirst = first_article;
  245.     lastart = last_article;
  246.     expire(first_article <= last_article ? extra_expire : FALSE);
  247.     }
  248.     trim_roots();
  249.     order_roots();
  250.     trim_authors();
  251.  
  252.     total.first = first_article;
  253.     total.last = last_article;
  254. #ifdef USE_NNTP
  255.     extra_expire = orig_extra;
  256. #endif
  257. }
  258.  
  259. /* Search all articles for numbers less than new_first.  Traverse the list
  260. ** using the domain links so we don't have to deal with the tree structure.
  261. ** If extra is true, list all articles in the directory to setup a bitmap
  262. ** with the existing articles marked as 'read', and drop everything that
  263. ** isn't there.
  264. */
  265. void
  266. expire(extra)
  267. bool_int extra;
  268. {
  269.     register DOMAIN *domain;
  270.     register ARTICLE *article, *next_art, *hold;
  271.     register ART_NUM art;
  272. #ifdef USE_NNTP
  273.     static int listgroup_type = CHECK_LISTGROUP;
  274.     extern char line[]; /* line contains the group name */
  275. #else
  276.     register DIR *dirp;
  277. #endif
  278.  
  279.     if (extra) {
  280.     MEM_SIZE ctlsize;
  281.  
  282.     /* Allocate a bitmap large enough for absfirst thru lastart. */
  283.     ctlsize = (MEM_SIZE)(OFFSET(lastart)/BITSPERBYTE+20);
  284.     ctlarea = safemalloc(ctlsize);
  285.     bzero(ctlarea, ctlsize);
  286.  
  287.     /* List all articles and use ctl_set() to keep track of what's there. */
  288. #ifdef USE_NNTP
  289. try_again:
  290.     switch (listgroup_type) {
  291.     case GOOD_LISTGROUP:
  292.         nntp_command("LISTGROUP");
  293.         (void)nntp_check(FALSE);
  294.         break;
  295.     case BAD_LISTGROUP:
  296.         sprintf(ser_line, "LISTGROUP %s", line);
  297.         nntp_command(ser_line);
  298.         (void)nntp_check(FALSE);
  299.         break;
  300.     case CHECK_LISTGROUP:
  301.         /* Check if LISTGROUP is available. */
  302.         nntp_command("LISTGROUP");
  303.         if (nntp_check(FALSE) == NNTP_CLASS_OK) {
  304.         listgroup_type = GOOD_LISTGROUP;
  305.         } else if (atoi(ser_line) == NNTP_SYNTAX_VAL) {
  306.         /* A command syntax error (not an unrecongnized command) is
  307.         ** the LISTGROUP that takes a newsgroup name. */
  308.         listgroup_type = BAD_LISTGROUP;
  309.         goto try_again;
  310.         } else {
  311.         listgroup_type = NO_LISTGROUP;
  312.         goto try_again;
  313.         }
  314.         break;
  315.     default:
  316.         sprintf(ser_line,"XHDR lines %ld-%ld",(long)absfirst,(long)lastart);
  317.         nntp_command(ser_line);
  318.         (void)nntp_check(FALSE);
  319.     }
  320.     if (*ser_line == NNTP_CLASS_OK) {
  321.         while (1) {
  322.         if (nntp_gets(buf, sizeof buf) < 0) {
  323.             extra = 0;
  324.             break;
  325.         }
  326.         if (*buf == '.') {
  327.             break;
  328.         }
  329.         art = atol(buf);
  330.         if (art >= absfirst && art <= lastart) {
  331.             ctl_set(art);
  332.         }
  333.         }
  334.     } else {
  335.         extra = 0;
  336.     }
  337. #else
  338.     if ((dirp = opendir(".")) != 0) {
  339.       register struct direct *dp;
  340.  
  341.         while ((dp = readdir(dirp)) != Null(struct direct *)) {
  342.         register char *p;
  343.  
  344.         for (p = dp->d_name; *p; p++) {
  345.             if (!isdigit(*p)) {
  346.             goto nope;
  347.             }
  348.         }
  349.         art = atol(dp->d_name);
  350.         if (art >= absfirst && art <= lastart) {
  351.             ctl_set(art);
  352.         }
  353.       nope: ;
  354.         }
  355.         closedir(dirp);
  356.     } else {
  357.         extra = 0;
  358.     }
  359. #endif
  360.     } else {
  361.     ctlarea = Nullch;
  362.     }
  363.  
  364.     for (domain = &unk_domain; domain; domain = next_domain) {
  365.     next_domain = domain->link;
  366.     for (article = domain->ids; article; article = next_art) {
  367.         next_art = article->id_link;
  368.         if (!article->subject) {
  369.         continue;
  370.         }
  371.         if (article->num < absfirst
  372.          || (extra && !ctl_check(article->num))) {
  373.         article->subject->count--;
  374.         article->subject = 0;
  375.         article->flags &= ~HAS_XREFS;
  376.         article->author->count--;
  377.         article->author = 0;
  378.         /* Free expired article if it has no children.  Then check
  379.         ** if the parent(s) are also fake and can be freed.  We'll
  380.         ** free any empty roots later.
  381.         */
  382.         while (!article->children) {
  383.             hold = article->parent;
  384.             unlink_child(article);
  385.             free_article(article);
  386.             if (hold && !hold->subject) {
  387.             if ((article = hold) == next_art) {
  388.                 next_art = next_art->id_link;
  389.             }
  390.             } else {
  391.             break;
  392.             }
  393.         }
  394.         expired_count++;
  395.         }/* if */
  396.     }/* for */
  397.     }/* for */
  398.     next_domain = Null(DOMAIN*);
  399.  
  400.     safefree(&ctlarea);
  401. }
  402.  
  403. /* Trim the article chains down so that we don't have more than one faked
  404. ** article between the root and any real ones.
  405. */
  406. void
  407. trim_roots()
  408. {
  409.     register ROOT *root, *last_root;
  410.     register ARTICLE *article, *next;
  411.     register SUBJECT *subject, *last_subj;
  412.     register int found;
  413.  
  414. #ifndef lint
  415.     last_root = (ROOT *)&root_root;
  416. #else
  417.     last_root = Null(ROOT*);
  418. #endif
  419.     for (root = root_root; root; root = last_root->link) {
  420.     for (article = root->articles; article; article = article->siblings) {
  421.         /* If an article has no subject, it is a "fake" reference node.
  422.         ** If all of its immediate children are also fakes, delete it
  423.         ** and graduate the children to the root.  If everyone is fake,
  424.         ** the chain dies.
  425.         */
  426.         while (!article->subject) {
  427.         found = 0;
  428.         for (next = article->children; next; next = next->siblings) {
  429.             if (next->subject) {
  430.             found = 1;
  431.             break;
  432.             }
  433.         }
  434.         if (!found) {
  435.             /* Remove this faked article and move all its children
  436.             ** up to the root.
  437.             */
  438.             next = article->children;
  439.             unlink_child(article);
  440.             free_article(article);
  441.             for (article = next; article; article = next) {
  442.             next = article->siblings;
  443.             article->parent = Nullart;
  444.             link_child(article);
  445.             }
  446.             article = root->articles;    /* start this root over */
  447.         } else {
  448.             break;            /* else, on to next article */
  449.         }
  450.         }
  451.     }
  452.     /* Free all unused subject strings.  Begin by trying to find a
  453.     ** subject for the root's pointer.
  454.     */
  455.     for (subject = root->subjects; subject && !subject->count; subject = root->subjects) {
  456.         root->subjects = subject->link;
  457.         free_subject(subject);
  458.         root->subject_cnt--;
  459.     }
  460.     /* Then free up any unused intermediate subjects.
  461.     */
  462.     if ((last_subj = subject) != Null(SUBJECT*)) {
  463.         while ((subject = subject->link) != Null(SUBJECT*)) {
  464.         if (!subject->count) {
  465.             last_subj->link = subject->link;
  466.             free_subject(subject);
  467.             root->subject_cnt--;
  468.             subject = last_subj;
  469.         } else {
  470.             last_subj = subject;
  471.         }
  472.         }
  473.     }
  474.     /* Now, free all roots without articles.  Flag unexpeced errors.
  475.     */
  476.     if (!root->articles) {
  477.         if (root->subjects) {
  478.         log_error("** Empty root still had subjects remaining! **\n");
  479.         }
  480.         last_root->link = root->link;
  481.         free_root(root);
  482.     } else {
  483.         last_root = root;
  484.     }
  485.     }
  486. }
  487.  
  488. /* Descend the author list, find any author names that aren't used
  489. ** anymore and free them.
  490. */
  491. void
  492. trim_authors()
  493. {
  494.     register AUTHOR *author, *last_author;
  495.  
  496. #ifndef lint
  497.     last_author = (AUTHOR *)&author_root;
  498. #else
  499.     last_author = Null(AUTHOR*);
  500. #endif
  501.     for (author = author_root; author; author = last_author->link) {
  502.     if (!author->count) {
  503.         last_author->link = author->link;
  504.         free_author(author);
  505.     } else {
  506.         last_author = author;
  507.     }
  508.     }
  509. }
  510.  
  511. /* Reorder the roots to place the oldest ones first (age determined by
  512. ** date of oldest article).
  513. */
  514. void
  515. order_roots()
  516. {
  517.     register ROOT *root, *next, *search, *link;
  518.  
  519.     /* If we don't have at least two roots, we're done! */
  520.     if (!(root = root_root) || !(next = root->link)) {
  521.     return;                        /* RETURN */
  522.     }
  523.     /* Break the old list off after the first root, and then start
  524.     ** inserting the roots into the list by date.
  525.     */
  526.     root->link = Null(ROOT*);
  527.     while ((root = next) != Null(ROOT*)) {
  528.     next = next->link;
  529.     if ((search = root_root)->articles->date >= root->articles->date) {
  530.         root->link = root_root;
  531.         root_root = root;
  532.     } else {
  533.         register time_t radate = root->articles->date;
  534.  
  535.         while ((link = search->link) != NULL
  536.          && link->articles->date < radate) {
  537.         search = link;
  538.         }
  539.         root->link = link;
  540.         search->link = root;
  541.     }
  542.     }
  543. }
  544.  
  545. #define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
  546.  
  547. /* Parse the subject into 72 characters or less.  Remove any "Re[:^]"s from
  548. ** the front (noting that it's there), and any "(was: old)" stuff from
  549. ** the end.  Then, compact multiple whitespace characters into one space,
  550. ** trimming leading/trailing whitespace.  If it's still too long, unmercifully
  551. ** cut it off.  We don't bother with subject continuation lines either.
  552. */
  553. void
  554. get_subject_str(str)
  555. register char *str;
  556. {
  557.     register char *cp;
  558.     register int len;
  559.  
  560.     while (*str && (unsigned char)*str <= ' ') {
  561.     str++;
  562.     }
  563.     if (!*str) {
  564.     bcopy("<None>", subject_str, 7);
  565.     return;                        /* RETURN */
  566.     }
  567.     cp = str;
  568.     while (EQ(cp[0], 'r') && EQ(cp[1], 'e')) {    /* check for Re: */
  569.     cp += 2;
  570.     if (*cp == '^') {                /* allow Re^2: */
  571.         while (*++cp <= '9' && *cp >= '0') {
  572.         ;
  573.         }
  574.     }
  575.     if (*cp != ':') {
  576.         break;
  577.     }
  578.     while (*++cp == ' ') {
  579.         ;
  580.     }
  581.     found_Re = 1;
  582.     str = cp;
  583.     }
  584.     /* Remove "(was: oldsubject)", because we already know the old subjects.
  585.     ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
  586.     */
  587.     for (cp = str; (cp = index(cp+1, '(')) != Nullch;) {
  588.     while (*++cp == ' ') {
  589.         ;
  590.     }
  591.     if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
  592.      && (cp[3] == ':' || cp[3] == ' '))
  593.     {
  594.         *--cp = '\0';
  595.         break;
  596.     }
  597.     if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
  598.      && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
  599.         *--cp = '\0';
  600.         break;
  601.     }
  602.     }
  603.     /* Copy subject to a temporary string, compacting multiple spaces/tabs */
  604.     for (len = 0, cp = subject_str; len < 72 && *str; len++) {
  605.     if ((unsigned char)*str <= ' ') {
  606.         while (*++str && (unsigned char)*str <= ' ') {
  607.         ;
  608.         }
  609.         *cp++ = ' ';
  610.     } else {
  611.         *cp++ = *str++;
  612.     }
  613.     }
  614.     if (cp[-1] == ' ') {
  615.     cp--;
  616.     }
  617.     *cp = '\0';
  618. }
  619.  
  620. #ifndef OLD_AUTHOR_CODE
  621. /* Name-munging routines written by Ross Ridge.  Public Domain.
  622. ** Enhanced by Wayne Davison.
  623. */
  624.  
  625. /* If necessary, compress a net user's full name by playing games with
  626. ** initials and the middle name(s).  If we start with "Ross Douglas Ridge"
  627. ** we try "Ross D Ridge", "Ross Ridge", "R D Ridge" and finally "R Ridge"
  628. ** before simply truncating the thing.  We also turn "R. Douglas Ridge"
  629. ** into "Douglas Ridge" and "Ross Ridge D.D.S." into "Ross Ridge" as a
  630. ** first step of the compaction, if needed.
  631. */
  632. static char *
  633. compress_name(name, max)
  634. char *name;
  635. int max;
  636. {
  637.     register char *s, *last, *mid, *d;
  638.     register int len, namelen, midlen;
  639.     int notlast;
  640.  
  641.     /* First remove white space from both ends. */
  642.     while (isspace(*name)) {
  643.     name++;
  644.     }
  645.     if ((len = strlen(name)) == 0) {
  646.     return name;
  647.     }
  648.     s = name + len - 1;
  649.     while (isspace(*s)) {
  650.     s--;
  651.     }
  652.     s[1] = '\0';
  653.     if (s - name + 1 <= max) {
  654.     return name;
  655.     }
  656.  
  657.     /* Look for characters that likely mean the end of the name
  658.     ** and the start of some hopefully uninteresting additional info.
  659.     ** Spliting at a comma is somewhat questionalble, but since
  660.     ** "Ross Ridge, The Great HTMU" comes up much more often than 
  661.     ** "Ridge, Ross" and since "R HTMU" is worse than "Ridge" we do
  662.     ** it anyways.
  663.     */
  664.     for (d = name + 1; *d; d++) {
  665.     if (*d == ',' || *d == ';' || *d == '(' || *d == '@'
  666.      || (*d == '-' && (d[1] == '-' || d[1] == ' '))) {
  667.         *d-- = '\0';
  668.         s = d;
  669.         break;
  670.     }
  671.     }
  672.  
  673.     /* Find the last name */
  674.     do {
  675.     notlast = 0;
  676.     while (isspace(*s)) {
  677.         s--;
  678.     }
  679.     s[1] = '\0';
  680.     len = s - name + 1;
  681.     if (len <= max) {
  682.         return name;
  683.     }
  684.     /* If the last name is an abbreviation it's not the one we want. */
  685.     if (*s == '.')
  686.         notlast = 1;
  687.     while (!isspace(*s)) {
  688.         if (s == name) {    /* only one name */
  689.         name[max] = '\0';
  690.         return name;
  691.         }
  692.         if (isdigit(*s)) {    /* probably a phone number */
  693.         notlast = 1;    /* so chuck it */
  694.         }
  695.         s--;
  696.     }
  697.     } while (notlast);
  698.  
  699.     last = s-- + 1;
  700.  
  701.     /* Look for a middle name */
  702.     while (isspace(*s)) {    /* get rid of any extra space */
  703.     len--;    
  704.     s--;
  705.     }
  706.     mid = name;
  707.     while (!isspace(*mid)) {
  708.     mid++;
  709.     }
  710.     namelen = mid - name + 1;
  711.     if (mid == s+1) {    /* no middle name */
  712.     mid = 0;
  713.     midlen = 0;
  714.     } else {
  715.     *mid++ = '\0';
  716.     while (isspace(*mid)) {
  717.         len--;
  718.         mid++;
  719.     }
  720.     midlen = s - mid + 2;
  721.     /* If first name is an initial and middle isn't and it all fits
  722.     ** without the first initial, drop it. */
  723.     if (len > max && mid != s && mid[1] != '.'
  724.      && (!name[1] || (name[1] == '.' && !name[2]))
  725.      && len - namelen <= max) {
  726.         len -= namelen;
  727.         name = mid;
  728.         mid = 0;
  729.     }
  730.     }
  731.     s[1] = '\0';
  732.     if (mid && len > max) {
  733.     /* Turn middle names into intials */
  734.     len -= s - mid + 2;
  735.     d = s = mid;
  736.     while (*s) {
  737.         if (isalpha(*s)) {
  738.         if (d != mid) {
  739.             *d++ = ' ';
  740.         }
  741.         *d++ = *s++;
  742.         }
  743.         while (*s && !isspace(*s)) {
  744.         s++;
  745.         }
  746.         while (isspace(*s)) {
  747.         s++;
  748.         }
  749.     }
  750.     if (d != mid) {
  751.         *d = '\0';
  752.         midlen = d - mid + 1;
  753.         len += midlen;
  754.     } else {
  755.         mid = 0;
  756.     }
  757.     }
  758.     if (len > max) {
  759.     /* If the first name fits without the middle initials, drop them */
  760.     if (mid && len - midlen <= max) {
  761.         len -= midlen;
  762.         mid = 0;
  763.     } else {
  764.         /* Turn the first name into an initial */
  765.         len -= namelen - 2;
  766.         name[1] = '\0';
  767.         namelen = 2;
  768.         if (len > max) {
  769.         /* Dump the middle initials (if present) */
  770.         if (mid) {
  771.             len -= midlen;
  772.             mid = 0;
  773.         }
  774.         if (len > max) {
  775.             /* Finally just truncate the last name */
  776.             last[max - 2] = '\0';
  777.         }
  778.         }
  779.     }
  780.     }
  781.  
  782.     /* Paste the names back together */
  783.     d = name + namelen;
  784.     if (mid) {
  785.     d[-1] = ' ';
  786.     strcpy(d, mid);
  787.     d += midlen;
  788.     }
  789.     d[-1] = ' ';
  790.     strcpy(d, last);
  791.     return name;
  792. }
  793.  
  794. /* Compress an email address, trying to keep as much of the local part of
  795. ** the addresses as possible.  The order of precence is @ ! %, but
  796. ** @ % ! may be better...
  797. */
  798. static char *
  799. compress_address(name, max)
  800. char *name;
  801. int max;
  802. {
  803.     char *s, *at, *bang, *hack, *start;
  804.     int len;
  805.  
  806.     /* Remove white space from both ends. */
  807.     while (isspace(*name)) {
  808.     name++;
  809.     }
  810.     if ((len = strlen(name)) == 0) {
  811.     return name;
  812.     }
  813.     s = name + len - 1;
  814.     while (isspace(*s)) {
  815.     s--;
  816.     }
  817.     s[1] = '\0';
  818.     if (*name == '<') {
  819.     name++;
  820.     if (*s == '>') {
  821.         *s-- = '\0';
  822.     }
  823.     }
  824.     if ((len = s - name + 1) <= max) {
  825.     return name;
  826.     }
  827.  
  828.     at = bang = hack = NULL;
  829.     for (s = name + 1; *s; s++) {
  830.     /* If there's whitespace in the middle then it's probably not
  831.     ** really an email address. */
  832.     if (isspace(*s)) {
  833.         name[max] = '\0';
  834.         return name;
  835.     }
  836.     switch (*s) {
  837.     case '@':
  838.         if (at == NULL) {
  839.         at = s;
  840.         }
  841.         break;
  842.     case '!':
  843.         if (at == NULL) {
  844.         bang = s;
  845.         hack = NULL;
  846.         }
  847.         break;
  848.     case '%':
  849.         if (at == NULL && hack == NULL) {
  850.         hack = s;
  851.         }
  852.         break;
  853.     }
  854.     }
  855.     if (at == NULL) {
  856.     at = name + len;
  857.     }
  858.  
  859.     if (hack != NULL) {
  860.     if (bang != NULL) {
  861.         if (at - bang - 1 >= max) {
  862.         start = bang + 1;
  863.         } else if (at - name >= max) {
  864.         start = at - max;
  865.         } else {
  866.         start = name;
  867.         }
  868.     } else {
  869.         start = name;
  870.     }
  871.     } else if (bang != NULL) {
  872.     if (at - name >= max) {
  873.         start = at - max;
  874.     } else {
  875.         start = name;
  876.     }
  877.     } else {
  878.     start = name;
  879.     }
  880.     if (len - (start - name) > max) {
  881.     start[max] = '\0';
  882.     }
  883.     return start;
  884. }
  885.  
  886. /* Extract the full-name part of an email address, returning NULL if not
  887. ** found.
  888. */
  889. static char *
  890. extract_name(name)
  891. char *name;
  892. {
  893.     char *s;
  894.     char *lparen, *rparen;
  895.     char *langle;
  896.  
  897.     while (isspace(*name)) {
  898.     name++;
  899.     }
  900.  
  901.     lparen = index(name, '(');
  902.     rparen = rindex(name, ')');
  903.     langle = index(name, '<');
  904.     if (!lparen && !langle) {
  905.     return NULL;
  906.     } else
  907.     if (langle && (!lparen || !rparen || lparen > langle || rparen < langle)) {
  908.     if (langle == name) {
  909.         return NULL;
  910.     }
  911.     *langle = '\0';
  912.     } else {
  913.     name = lparen;
  914.     *name++ = '\0';
  915.     while (isspace(*name)) {
  916.         name++;
  917.     }
  918.     if (name == rparen) {
  919.         return NULL;
  920.     }
  921.     if (rparen != NULL) {
  922.         *rparen = '\0';
  923.     }
  924.     }
  925.  
  926.     if (*name == '"') {
  927.     name++;
  928.     while (isspace(*name)) {
  929.         name++;
  930.     }
  931.     if ((s = rindex(name, '"')) != NULL) {
  932.         *s = '\0';
  933.     }
  934.     }
  935.     return name;
  936. }
  937.  
  938. /* Try to fit the author name in 16 bytes.  Use the comment portion if
  939. ** present.
  940. */
  941. void
  942. get_author_str(addr)
  943. char *addr;
  944. {
  945.     char *s;
  946.  
  947.     /* TODO: Do we need to eliminate ctrl chars here? */
  948.     if ((s = extract_name(addr)) != NULL) {
  949.     s = compress_name(s, 16);
  950.     } else {
  951.     s = compress_address(addr, 16);
  952.     }
  953.     strcpy(author_str, s);
  954. }
  955.  
  956. #else  /* Here's the old, simple method in case someone wants it. */
  957.  
  958. /* Try to fit the author name in 16 bytes.  Use the comment portion in
  959. ** parenthesis if present.  Cut off non-commented names at the '@' or '%'.
  960. ** Then, put as many characters as we can into the 16 bytes, packing multiple
  961. ** whitespace characters into a single space.
  962. */
  963. void
  964. get_author_str(str)
  965. char *str;
  966. {
  967.     register char *cp, *cp2;
  968.  
  969.     if ((cp = index(str, '(')) != Nullch) {
  970.     str = cp+1;
  971.     if ((cp = rindex(str, ')')) != Nullch) {
  972.         *cp = '\0';
  973.     }
  974.     } else {
  975.     if ((cp = index(str, '@')) != Nullch) {
  976.         *cp = '\0';
  977.     }
  978.     if ((cp = index(str, '%')) != Nullch) {
  979.         *cp = '\0';
  980.     }
  981.     }
  982.     for (cp = str, cp2 = author_str; *cp && cp2-author_str < 16;) {
  983.     /* Pack white space and turn ctrl-chars into spaces. */
  984.     if (*cp <= ' ') {
  985.         while (*++cp && *cp <= ' ') {
  986.         ;
  987.         }
  988.         if (cp2 != author_str) {
  989.         *cp2++ = ' ';
  990.         }
  991.     } else {
  992.         *cp2++ = *cp++;
  993.     }
  994.     }
  995.     *cp2 = '\0';
  996. }
  997. #endif
  998.  
  999. /* Take a message-id and see if we already know about it.  If so, return it.
  1000. ** If not, create it.  We separate the id into its id@domain parts, and
  1001. ** link all the unique ids to one copy of the domain portion.  This saves
  1002. ** a bit of space.
  1003. */
  1004. ARTICLE *
  1005. get_article(msg_id)
  1006. char *msg_id;
  1007. {
  1008.     register DOMAIN *domain;
  1009.     register ARTICLE *article;
  1010.     register char *cp, *after_at;
  1011.  
  1012.     /* Take message id, break it up into <id@domain>, and try to match it.
  1013.     */
  1014.     while (*msg_id == ' ') {
  1015.     msg_id++;
  1016.     }
  1017.     cp = msg_id + strlen(msg_id) - 1;
  1018.     if (msg_id >= cp) {
  1019.     if (log_verbosity) {
  1020.         log_error("Message-ID is empty! [%ld]\n", num);
  1021.     }
  1022.     return Nullart;
  1023.     }
  1024.     if (*msg_id++ != '<') {
  1025.     if (log_verbosity) {
  1026.         log_error("Message-ID doesn't start with '<' [%ld]\n", num);
  1027.     }
  1028.     msg_id--;
  1029.     }
  1030.     if (*cp != '>') {
  1031.     if (log_verbosity) {
  1032.         log_error("Message-ID doesn't end with '>' [%ld]\n", num);
  1033.     }
  1034.     cp++;
  1035.     }
  1036.     *cp = '\0';
  1037.     if (msg_id == cp) {
  1038.     if (log_verbosity) {
  1039.         log_error("Message-ID is null! [%ld]\n", num);
  1040.     }
  1041.     return Nullart;
  1042.     }
  1043.  
  1044.     if ((after_at = index(msg_id, '@')) == Nullch) {
  1045.     domain = &unk_domain;
  1046.     } else {
  1047.     *after_at++ = '\0';
  1048.     for (cp = after_at; *cp; cp++) {
  1049.         if (isupper(*cp)) {
  1050.         *cp = tolower(*cp);        /* lower-case domain portion */
  1051.         }
  1052.     }
  1053.     *cp = '\0';
  1054.     /* Try to find domain name in database. */
  1055.     for (domain = unk_domain.link; domain; domain = domain->link) {
  1056.         if (strEQ(domain->name, after_at)) {
  1057.         break;
  1058.         }
  1059.     }
  1060.     if (!domain) {        /* if domain doesn't exist, create it */
  1061.       register int len = cp - after_at + 1;
  1062.         domain = (DOMAIN *)safemalloc(sizeof (DOMAIN));
  1063.         total.domain++;
  1064.         domain->name = safemalloc(len);
  1065.         total.string2 += len;
  1066.         bcopy(after_at, domain->name, len);
  1067.         domain->ids = Nullart;
  1068.         domain->link = unk_domain.link;
  1069.         unk_domain.link = domain;
  1070.     }
  1071.     }
  1072.     /* Try to find id in this domain. */
  1073.     for (article = domain->ids; article; article = article->id_link) {
  1074.     if (strEQ(article->id, msg_id)) {
  1075.         break;
  1076.     }
  1077.     }
  1078.     if (!article) {        /* If it doesn't exist, create an article */
  1079.     register int len = strlen(msg_id) + 1;
  1080.     article = (ARTICLE *)safemalloc(sizeof (ARTICLE));
  1081.     bzero(article, sizeof (ARTICLE));
  1082.     total.article++;
  1083.     article->num = 0;
  1084.     article->id = safemalloc(len);
  1085.     total.string2 += len;
  1086.     bcopy(msg_id, article->id, len);
  1087.     article->domain = domain;
  1088.     article->id_link = domain->ids;
  1089.     domain->ids = article;
  1090.     }
  1091.     return article;
  1092. }
  1093.  
  1094. /* Take all the data we've accumulated about the article and shove it into
  1095. ** the article tree at the best place we can possibly imagine.
  1096. */
  1097. void
  1098. insert_article(article, date)
  1099. ARTICLE *article;
  1100. time_t date;
  1101. {
  1102.     register ARTICLE *node, *last;
  1103.     register char *cp, *end;
  1104. #ifndef USE_NNTP
  1105.     int len;
  1106. #endif
  1107.  
  1108.     if (article->subject) {
  1109.     if (log_verbosity) {
  1110.         log_error("We've already seen article #%ld (%s@%s)\n",
  1111.         num, article->id, article->domain->name);
  1112.     }
  1113.     return;                        /* RETURN */
  1114.     }
  1115.     article->date = date;
  1116.     article->num = num;
  1117.     article->flags = 0;
  1118.  
  1119.     if (!*references && found_Re) {
  1120.     if (log_verbosity > 1) {
  1121.         log_error("Missing reference line!  [%ld]\n", num);
  1122.     }
  1123.     }
  1124.     /* If the article has a non-zero root, it is already in a thread somewhere.
  1125.     ** Unlink it to try to put it in the best possible spot.
  1126.     */
  1127.     if (article->root) {
  1128.     /* Check for a real or shared-fake parent.  Articles that have never
  1129.     ** existed have a num of 0.  Expired articles that remain as references
  1130.     ** have a valid num.  (Valid date too, but no subject.)
  1131.     */
  1132.     for (node = article->parent;
  1133.          node && !node->num && node->child_cnt == 1;
  1134.          node = node->parent)
  1135.     {
  1136.         ;
  1137.     }
  1138.     unlink_child(article);
  1139.     if (node) {            /* do we have decent parents? */
  1140.         /* Yes: assume that our references are ok, and just reorder us
  1141.         ** with our siblings by date.
  1142.         */
  1143.         link_child(article);
  1144.         use_root(article, article->root);
  1145.         /* Freshen the date in any faked parent articles. */
  1146.         for (node = article->parent;
  1147.          node && !node->num && date < node->date;
  1148.          node = node->parent)
  1149.         {
  1150.         node->date = date;
  1151.         unlink_child(node);
  1152.         link_child(node);
  1153.         }
  1154.         return;                    /* RETURN */
  1155.     }
  1156.     /* We'll assume that this article has as good or better references
  1157.     ** than the child that faked us initially.  Free the fake reference-
  1158.     ** chain and process our references as usual.
  1159.     */
  1160.     for (node = article->parent; node; node = last) {
  1161.         unlink_child(node);
  1162.         last = node->parent;
  1163.         free_article(node);
  1164.     }
  1165.     article->parent = Nullart;        /* neaten up */
  1166.     article->siblings = Nullart;
  1167.     }
  1168.   check_references:
  1169.     if (!*references) {    /* If no references but "Re:" in subject, */
  1170.     if (found_Re) {    /* search for a reference in any cited text */
  1171. #ifndef USE_NNTP
  1172.         for (len = 4; len && fgets(buf, sizeof buf, fp_article); len--) {
  1173.         if ((cp = index(buf, '<')) && (end = index(cp, ' '))) {
  1174.             if (end[-1] == ',') {
  1175.             end--;
  1176.             }
  1177.             *end = '\0';
  1178.             if ((end = index(cp, '>')) == Nullch) {
  1179.             end = cp + strlen(cp) - 1;
  1180.             }
  1181.             if (valid_message_id(cp, end)) {
  1182.             strcpy(references+1, cp);
  1183.             *references = ' ';
  1184.             if (log_verbosity > 2) {
  1185.                 log_error("Found cited-text reference: '%s' [%ld]\n",
  1186.                 references+1, num);
  1187.             }
  1188.             break;
  1189.             }
  1190.         }
  1191.         }
  1192. #endif
  1193.     } else {
  1194.         article->flags |= ROOT_ARTICLE;
  1195.     }
  1196.     }
  1197.     /* If we have references, process them from the right end one at a time
  1198.     ** until we either run into somebody, or we run out of references.
  1199.     */
  1200.     if (*references) {
  1201.     last = article;
  1202.     node = Nullart;
  1203.     end = references + strlen(references) - 1;
  1204.     while ((cp = rindex(references, '<')) != Nullch) {
  1205.         while (end >= cp && ((unsigned char)*end <= ' ' || *end == ',')) {
  1206.         end--;
  1207.         }
  1208.         end[1] = '\0';
  1209.         /* Quit parsing references if this one is garbage. */
  1210.         if (!valid_message_id(cp, end)) {
  1211.         if (log_verbosity) {
  1212.             log_error("Bad ref '%s' [%ld]\n", cp, num);
  1213.         }
  1214.         break;
  1215.         }
  1216.         /* Dump all domains that end in '.', such as "..." & "1@DEL." */
  1217.         if (end[-1] == '.') {
  1218.         break;
  1219.         }
  1220.         node = get_article(cp);
  1221.         *cp = '\0';
  1222.  
  1223.         /* Check for duplicates on the reference line.  Brand-new data has
  1224.         ** no date.  Data we just allocated earlier on this line has a
  1225.         ** date but no root.  Special-case the article itself, since it
  1226.         ** MIGHT have a root.
  1227.         */
  1228.         if ((node->date && !node->root) || node == article) {
  1229.         if (log_verbosity) {
  1230.             log_error("Reference line contains duplicates [%ld]\n",
  1231.             num);
  1232.         }
  1233.         if ((node = last) == article) {
  1234.             node = Nullart;
  1235.         }
  1236.         continue;
  1237.         }
  1238.         last->parent = node;
  1239.         link_child(last);
  1240.         if (node->root) {
  1241.         break;
  1242.         }
  1243.         node->date = date;
  1244.         last = node;
  1245.         end = cp-1;
  1246.     }
  1247.     if (!node) {
  1248.         *references = '\0';
  1249.         goto check_references;
  1250.     }
  1251.     /* Check if we ran into anybody that was already linked.  If so, we
  1252.     ** just use their root.
  1253.     */
  1254.     if (node->root) {
  1255.         /* See if this article spans the gap between what we thought
  1256.         ** were two different roots.
  1257.         */
  1258.         if (article->root && article->root != node->root) {
  1259.         merge_roots(node->root, article->root);
  1260.         /* Set the roots of any children we brought with us. */
  1261.         set_root(article, node->root);
  1262.         }
  1263.         use_root(article, node->root);
  1264.     } else {
  1265.         /* We didn't find anybody we knew, so either create a new root or
  1266.         ** use the article's root if it was previously faked.
  1267.         */
  1268.         if (!article->root) {
  1269.         make_root(node);
  1270.         use_root(article, node->root);
  1271.         } else {
  1272.         node->root = article->root;
  1273.         link_child(node);
  1274.         use_root(article, article->root);
  1275.         }
  1276.     }
  1277.     /* Set the roots of the faked articles we created as references. */
  1278.     for (node = article->parent; node && !node->root; node = node->parent) {
  1279.         node->root = article->root;
  1280.     }
  1281.     /* Make sure we didn't circularly link to a child article(!), by
  1282.     ** ensuring that we run into the root before we run into ourself.
  1283.     */
  1284.     while (node && node->parent != article) {
  1285.         node = node->parent;
  1286.     }
  1287.     if (node) {
  1288.         /* Ugh.  Someone's tweaked reference line with an incorrect
  1289.         ** article-order arrived first, and one of our children is
  1290.         ** really one of our ancestors. Cut off the bogus child branch
  1291.         ** right where we are and link it to the root.
  1292.         */
  1293.         if (log_verbosity) {
  1294.         log_error("Found ancestral child -- fixing.\n");
  1295.         }
  1296.         unlink_child(node);
  1297.         node->parent = Nullart;
  1298.         link_child(node);
  1299.     }
  1300.     } else {
  1301.     /* The article has no references.  Either turn it into a new root, or
  1302.     ** re-attach fleshed-out (previously faked) article to its old root.
  1303.     */
  1304.     if (!article->root) {
  1305.         make_root(article);
  1306.     } else {
  1307.         link_child(article);
  1308.         use_root(article, article->root);
  1309.     }
  1310.     }
  1311. }
  1312.  
  1313. /* Check if the string we've found looks like a valid message-id reference.
  1314. */
  1315. int
  1316. valid_message_id(start, end)
  1317. register char *start, *end;
  1318. {
  1319.     char *mid;
  1320.  
  1321.     if (start == end) {
  1322.     return 0;
  1323.     }
  1324.  
  1325.     if (*end != '>') {
  1326.     /* Compensate for space cadets who include the header in their
  1327.     ** subsitution of all '>'s into another citation character.
  1328.     */
  1329.     if (*end == '<' || *end == '-' || *end == '!' || *end == '%'
  1330.      || *end == ')' || *end == '|' || *end == ':' || *end == '}'
  1331.      || *end == '*' || *end == '+' || *end == '#' || *end == ']'
  1332.      || *end == '@' || *end == '$') {
  1333.         if (log_verbosity) {
  1334.         log_error("Reference ended in '%c' [%ld]\n", *end, num);
  1335.         }
  1336.         *end = '>';
  1337.     }
  1338.     } else if (end[-1] == '>') {
  1339.     if (log_verbosity) {
  1340.         log_error("Reference ended in '>>' [%ld]\n", num);
  1341.     }
  1342.     *(end--) = '\0';
  1343.     }
  1344.     /* Id must be "<...@...>" */
  1345.     if (*start != '<' || *end != '>' || (mid = index(start, '@')) == Nullch
  1346.      || mid == start+1 || mid+1 == end) {
  1347.     return 0;                    /* RETURN */
  1348.     }
  1349.     return 1;
  1350. }
  1351.  
  1352. /* Remove an article from its parent/siblings.  Leave parent pointer intact.
  1353. */
  1354. void
  1355. unlink_child(child)
  1356. register ARTICLE *child;
  1357. {
  1358.     register ARTICLE *last;
  1359.  
  1360.     if (!(last = child->parent)) {
  1361.     child->root->thread_cnt--;
  1362.     if ((last = child->root->articles) == child) {
  1363.         child->root->articles = child->siblings;
  1364.     } else {
  1365.         goto sibling_search;
  1366.     }
  1367.     } else {
  1368.     last->child_cnt--;
  1369.     if (last->children == child) {
  1370.         last->children = child->siblings;
  1371.     } else {
  1372.         last = last->children;
  1373.       sibling_search:
  1374.         while (last->siblings != child) {
  1375.         last = last->siblings;
  1376.         }
  1377.         last->siblings = child->siblings;
  1378.     }
  1379.     }
  1380. }
  1381.  
  1382. /* Link an article to its parent article.  If its parent pointer is zero,
  1383. ** link it to its root.  Sorts siblings by date.
  1384. */
  1385. void
  1386. link_child(child)
  1387. register ARTICLE *child;
  1388. {
  1389.     register ARTICLE *node;
  1390.     register ROOT *root;
  1391.  
  1392.     if (!(node = child->parent)) {
  1393.     root = child->root;
  1394.     root->thread_cnt++;
  1395.     node = root->articles;
  1396.     if (!node || child->date < node->date) {
  1397.         child->siblings = node;
  1398.         root->articles = child;
  1399.     } else {
  1400.         goto sibling_search;
  1401.     }
  1402.     } else {
  1403.     node->child_cnt++;
  1404.     node = node->children;
  1405.     if (!node || child->date < node->date) {
  1406.         child->siblings = node;
  1407.         child->parent->children = child;
  1408.     } else {
  1409.       sibling_search:
  1410.         for (; node->siblings; node = node->siblings) {
  1411.         if (node->siblings->date > child->date) {
  1412.             break;
  1413.         }
  1414.         }
  1415.         child->siblings = node->siblings;
  1416.         node->siblings = child;
  1417.     }
  1418.     }
  1419. }
  1420.  
  1421. /* Create a new root for the specified article.  If the current subject_str
  1422. ** matches any pre-existing root's subjects, we'll instead add it on as a
  1423. ** parallel thread.
  1424. */
  1425. void
  1426. make_root(article)
  1427. register ARTICLE *article;
  1428. {
  1429.     register ROOT *new, *node;
  1430.     register SUBJECT *subject;
  1431.  
  1432. #ifndef NO_SUBJECT_MATCHING
  1433.     /* First, check the other root's subjects for a match. */
  1434.     for (node = root_root; node; node = node->link) {
  1435.     for (subject = node->subjects; subject; subject = subject->link) {
  1436.         if (subject_equal(subject->str, subject_str)) {
  1437.         use_root(article, node);        /* use it instead */
  1438.         link_child(article);
  1439.         return;                    /* RETURN */
  1440.         }
  1441.     }
  1442.     }
  1443. #endif
  1444.  
  1445.     /* Create a new root. */
  1446.     new = (ROOT *)safemalloc(sizeof (ROOT));
  1447.     total.root++;
  1448.     new->articles = article;
  1449.     new->root_num = article->num;
  1450.     new->thread_cnt = 1;
  1451.     if (article->num) {
  1452.     article->author = new_author();
  1453.     new->subject_cnt = 1;
  1454.     new->subjects = article->subject = new_subject();
  1455.     } else {
  1456.     new->subject_cnt = 0;
  1457.     new->subjects = Null(SUBJECT*);
  1458.     }
  1459.     article->root = new;
  1460.     new->link = root_root;
  1461.     root_root = new;
  1462. }
  1463.  
  1464. /* Add this article's subject onto the indicated root's list.  Point the
  1465. ** article at the root.
  1466. */
  1467. void
  1468. use_root(article, root)
  1469. ARTICLE *article;
  1470. ROOT *root;
  1471. {
  1472.     register SUBJECT *subject;
  1473.     register ROOT *root2;
  1474.     SUBJECT *hold, *child_subj = Null(SUBJECT*), *sib_subj = Null(SUBJECT*);
  1475.     ARTICLE *node;
  1476.  
  1477.     article->root = root;
  1478.  
  1479.     /* If it's a fake, there's no subject to add. */
  1480.     if (!article->num) {
  1481.     return;                        /* RETURN */
  1482.     }
  1483.  
  1484.     /* If we haven't picked a unique message number to represent this root,
  1485.     ** use the first non-zero number we encounter.  Which one doesn't matter.
  1486.     */
  1487.     if (!root->root_num) {
  1488.     root->root_num = article->num;
  1489.     }
  1490.     article->author = new_author();
  1491.  
  1492.     /* Check if the new subject matches any of the other subjects in this root.
  1493.     ** If so, we just update the count.  If not, check all the other roots for
  1494.     ** a match.  If found, the new subject is common between the two roots, so
  1495.     ** we merge the two roots together.
  1496.     */
  1497.     root2 = root;
  1498. #ifndef NO_SUBJECT_MATCHING
  1499.     do {
  1500. #endif
  1501.     for (subject = root2->subjects; subject; subject = subject->link) {
  1502.         if (subject_equal(subject->str, subject_str)) {
  1503.         article->subject = subject;
  1504.         subject->count++;
  1505. #ifndef NO_SUBJECT_MATCHING
  1506.         if (root2 != root) {
  1507.             merge_roots(root, root2);
  1508.         }
  1509. #endif
  1510.         return;                    /* RETURN */
  1511.         }
  1512.     }
  1513. #ifndef NO_SUBJECT_MATCHING
  1514.     if ((root2 = root2->link) == Null(ROOT*)) {
  1515.         root2 = root_root;
  1516.     }
  1517.     } while (root2 != root);
  1518. #endif
  1519.  
  1520.     article->subject = hold = new_subject();
  1521.     root->subject_cnt++;
  1522.  
  1523.     /* Find the subject of any pre-existing children or siblings.  We want
  1524.     ** to insert the new subject before one of these to keep the numbering
  1525.     ** intuitive in the newsreader.  Never insert prior to our parent's
  1526.     ** subject, however.
  1527.     */
  1528.     for (node = article->children; node; node = node->children) {
  1529.     if (node->subject) {
  1530.         child_subj = node->subject;
  1531.         break;
  1532.     }
  1533.     }
  1534.     for (node = article->siblings; node; node = node->siblings) {
  1535.     if (node->subject) {
  1536.         sib_subj = node->subject;
  1537.         break;
  1538.     }
  1539.     }
  1540.     if (article->parent) {
  1541.     if (article->parent->subject == child_subj) {
  1542.         child_subj = Null(SUBJECT*);
  1543.     }
  1544.     if (article->parent->subject == sib_subj) {
  1545.         sib_subj = Null(SUBJECT*);
  1546.     }
  1547.     }
  1548.     if (!(subject = root->subjects)
  1549.      || subject == child_subj || subject == sib_subj) {
  1550.     hold->link = root->subjects;
  1551.     root->subjects = hold;
  1552.     } else {
  1553.     while (subject->link
  1554.      && subject->link != child_subj && subject->link != sib_subj) {
  1555.         subject = subject->link;
  1556.     }
  1557.     hold->link = subject->link;
  1558.     subject->link = hold;
  1559.     }
  1560. }
  1561.  
  1562. /* Check subjects in a case-insignificant, punctuation-ignoring manner.
  1563. */
  1564. int
  1565. subject_equal(str1, str2)
  1566. register char *str1, *str2;
  1567. {
  1568.     register char ch1, ch2;
  1569.  
  1570.     while ((ch1 = *str1++)) {
  1571.     if (ch1 == ' ' || ispunct(ch1)) {
  1572.         while (*str1 && (*str1 == ' ' || ispunct(*str1))) {
  1573.         str1++;
  1574.         }
  1575.         ch1 = ' ';
  1576.     } else if (isupper(ch1)) {
  1577.         ch1 = tolower(ch1);
  1578.     }
  1579.     if (!(ch2 = *str2++)) {
  1580.         return 0;
  1581.     }
  1582.     if (ch2 == ' ' || ispunct(ch2)) {
  1583.         while (*str2 && (*str2 == ' ' || ispunct(*str2))) {
  1584.         str2++;
  1585.         }
  1586.         ch2 = ' ';
  1587.     } else if (isupper(ch2)) {
  1588.         ch2 = tolower(ch2);
  1589.     }
  1590.     if (ch1 != ch2) {
  1591.         return 0;
  1592.     }
  1593.     }
  1594.     if (*str2) {
  1595.     return 0;
  1596.     }
  1597.     return 1;
  1598. }
  1599.  
  1600. /* Create a new subject structure. */
  1601. SUBJECT *
  1602. new_subject()
  1603. {
  1604.     register int len = strlen(subject_str) + 1;
  1605.     register SUBJECT *subject;
  1606.  
  1607.     subject = (SUBJECT *)safemalloc(sizeof (SUBJECT));
  1608.     total.subject++;
  1609.     subject->count = 1;
  1610.     subject->link = Null(SUBJECT*);
  1611.     subject->str = safemalloc(len);
  1612.     total.string1 += len;
  1613.     bcopy(subject_str, subject->str, len);
  1614.  
  1615.     return subject;
  1616. }
  1617.  
  1618. /* Create a new author structure. */
  1619. AUTHOR *
  1620. new_author()
  1621. {
  1622.     register len = strlen(author_str) + 1;
  1623.     register AUTHOR *author, *last_author;
  1624.  
  1625.     last_author = Null(AUTHOR*);
  1626.     for (author = author_root; author; author = author->link) {
  1627. #ifndef DONT_COMPARE_AUTHORS    /* might like to define this to save time */
  1628.     if (strEQ(author->name, author_str)) {
  1629.         author->count++;
  1630.         return author;                /* RETURN */
  1631.     }
  1632. #endif
  1633.     last_author = author;
  1634.     }
  1635.  
  1636.     author = (AUTHOR *)safemalloc(sizeof (AUTHOR));
  1637.     total.author++;
  1638.     author->count = 1;
  1639.     author->link = Null(AUTHOR*);
  1640.     author->name = safemalloc(len);
  1641.     total.string1 += len;
  1642.     bcopy(author_str, author->name, len);
  1643.  
  1644.     if (last_author) {
  1645.     last_author->link = author;
  1646.     } else {
  1647.     author_root = author;
  1648.     }
  1649.     return author;
  1650. }
  1651.  
  1652. /* Insert all of root2 into root1, setting the proper root values and
  1653. ** updating subject counts.
  1654. */
  1655. void
  1656. merge_roots(root1, root2)
  1657. ROOT *root1, *root2;
  1658. {
  1659.     register ARTICLE *node, *next;
  1660.     register SUBJECT *subject;
  1661.  
  1662.     /* Remember whoever's root num is lower.  This could screw up a
  1663.     ** newsreader's kill-thread code if someone already saw the roots as
  1664.     ** being separate, but it must be done.  The newsreader code will have
  1665.     ** to handle this as best as it can.
  1666.     */
  1667.     if (root1->root_num > root2->root_num) {
  1668.     root1->root_num = root2->root_num;
  1669.     }
  1670.  
  1671.     for (node = root2->articles; node; node = next) {
  1672.     /* For each article attached to root2: detach it, set the branch's
  1673.     ** root pointer to root1, and then attach it to root1.
  1674.     */
  1675.     next = node->siblings;
  1676.     unlink_child(node);
  1677.     node->siblings = Nullart;
  1678.     set_root(node, root1);        /* sets children too */
  1679.     /* Link_child() depends on node->parent being null and node->root
  1680.     ** being set.
  1681.     */
  1682.     link_child(node);
  1683.     }
  1684.     root1->subject_cnt += root2->subject_cnt;
  1685.     if (!(subject = root1->subjects)) {
  1686.     root1->subjects = root2->subjects;
  1687.     } else {
  1688.     while (subject->link) {
  1689.         subject = subject->link;
  1690.     }
  1691.     subject->link = root2->subjects;
  1692.     }
  1693.     unlink_root(root2);
  1694.     free_root(root2);
  1695. }
  1696.  
  1697. /* When merging roots, we need to reset all the root pointers.
  1698. */
  1699. void
  1700. set_root(node, root)
  1701. ARTICLE *node;
  1702. ROOT *root;
  1703. {
  1704.     while (node) {
  1705.     node->root = root;
  1706.     if (node->children) {
  1707.         set_root(node->children, root);
  1708.     }
  1709.     node = node->siblings;
  1710.     }
  1711. }
  1712.  
  1713. /* Unlink a root from its neighbors. */
  1714. void
  1715. unlink_root(root)
  1716. register ROOT *root;
  1717. {
  1718.     register ROOT *node;
  1719.  
  1720.     if ((node = root_root) == root) {
  1721.     root_root = root->link;
  1722.     } else {
  1723.     while (node->link != root) {
  1724.         node = node->link;
  1725.     }
  1726.     node->link = root->link;
  1727.     }
  1728. }
  1729.  
  1730. /* Free an article and its message-id string.  All other resources must
  1731. ** already be free, and it must not be attached to any threads.
  1732. */
  1733. void
  1734. free_article(this)
  1735. ARTICLE *this;
  1736. {
  1737.     register ARTICLE *art;
  1738.  
  1739.     if ((art = this->domain->ids) == this) {
  1740.     if (!(this->domain->ids = this->id_link)) {
  1741.         free_domain(this->domain);
  1742.     }
  1743.     } else {
  1744.     while (this != art->id_link) {
  1745.         art = art->id_link;
  1746.     }
  1747.     art->id_link = this->id_link;
  1748.     }
  1749.     total.string2 -= strlen(this->id) + 1;
  1750.     free(this->id);
  1751.     free(this);
  1752.     total.article--;
  1753. }
  1754.  
  1755. /* Free the domain only when its last unique id has been freed. */
  1756. void
  1757. free_domain(this)
  1758. DOMAIN *this;
  1759. {
  1760.     register DOMAIN *domain;
  1761.  
  1762.     if (this == (domain = &unk_domain)) {
  1763.     return;
  1764.     }
  1765.     if (this == next_domain) {    /* help expire routine skip freed domains */
  1766.     next_domain = next_domain->link;
  1767.     }
  1768.     while (this != domain->link) {
  1769.     domain = domain->link;
  1770.     }
  1771.     domain->link = this->link;
  1772.     total.string2 -= strlen(this->name) + 1;
  1773.     free(this->name);
  1774.     free(this);
  1775.     total.domain--;
  1776. }
  1777.  
  1778. /* Free the subject structure and its string. */
  1779. void
  1780. free_subject(this)
  1781. SUBJECT *this;
  1782. {
  1783.     total.string1 -= strlen(this->str) + 1;
  1784.     free(this->str);
  1785.     free(this);
  1786.     total.subject--;
  1787. }
  1788.  
  1789. /* Free a root.  It must already be unlinked. */
  1790. void
  1791. free_root(this)
  1792. ROOT *this;
  1793. {
  1794.     free(this);
  1795.     total.root--;
  1796. }
  1797.  
  1798. /* Free the author structure when it's not needed any more. */
  1799. void
  1800. free_author(this)
  1801. AUTHOR *this;
  1802. {
  1803.     total.string1 -= strlen(this->name) + 1;
  1804.     free(this->name);
  1805.     free(this);
  1806.     total.author--;
  1807. }
  1808.  
  1809. #if defined(USE_NNTP) && !defined(HAS_USLEEP)
  1810. usleep(usec)
  1811. long usec;
  1812. {
  1813. # ifndef USELECT
  1814.     if (usec /= 1000000) {
  1815.     sleep((int)usec);
  1816.     }
  1817. # else
  1818.     struct timeval t;
  1819.  
  1820.     if (usec <= 0) {
  1821.     return;
  1822.     }
  1823.     t.tv_usec = usec % 1000000;
  1824.     t.tv_sec  = usec / 1000000;
  1825.     (void) select(1, 0, 0, 0, &t);
  1826. # endif
  1827. }
  1828. #endif
  1829.