home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / unix / volume27 / mthreads / part03 < prev    next >
Encoding:
Text File  |  1993-11-20  |  46.1 KB  |  1,878 lines

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