/* $Id: news.c 1.3 1995/01/08 21:26:16 cthuang Exp $ * * Get news from NNTP server. */ #include #include #include #include #include "socket.h" #include "nntp.h" #include "nntpcl.h" #include "souper.h" /* article number range in the .newsrc file */ typedef struct aRange { struct aRange *next; /* pointer to next */ int lo, hi; /* article number range */ } Range; /* newsgroup entry in the .newsrc file */ typedef struct aNewsrcGroup { struct aNewsrcGroup *next; /* pointer to next */ char *name; /* newsgroup name */ Range *readList; /* list of read article ranges */ char subscribed; /* subscribed flag */ } NewsrcGroup; static NewsrcGroup *nrcList; /* list of .newsrc entries. */ static long byteCount; /* current size of fetched news */ static char killEnabled; /* kill processing enabled for this group */ static FILE *tmpF; /* temporary file for article */ /* Read the article numbers from a .newsrc line. */ static Range * getReadList (FILE *nrcFile) { static const char digits[] = "%[0123456789]"; Range *pLast, *rp, *head; int lo, hi, c; char *range; char buf[20]; /* Initialize subscription list */ pLast = NULL; head = NULL; /* Expect [ \n] */ c = fgetc(nrcFile); while (c != '\n' && c != EOF) { /* Expect number */ if (fscanf(nrcFile, digits, buf) != 1) break; lo = atoi(buf); /* Get space for new list entry */ rp = (Range *)xmalloc(sizeof(Range)); /* Expect [-,\n] */ c = fgetc(nrcFile); if (c == '-') { /* Is a range */ /* Expect number */ if (fscanf(nrcFile, digits, buf) != 1) break; hi = atoi(buf); rp->lo = lo; rp->hi = hi; /* Reverse them in case they're backwards */ if (hi < lo) { rp->lo = hi; rp->hi = lo; } /* Expect [,\n] */ c = fgetc(nrcFile); } else { /* Not a range */ rp->lo = rp->hi = lo; } /* Check if range overlaps last one */ if (pLast != NULL && rp->lo <= pLast->hi + 1) { /* Combine ranges */ if (rp->lo < pLast->lo) pLast->lo = rp->lo; if (rp->hi > pLast->hi) pLast->hi = rp->hi; /* Free old one */ free(rp); } else { /* No overlap, update pointers */ if (pLast == NULL) { head = rp; } else { pLast->next = rp; } rp->next = NULL; pLast = rp; } } return head; } /* Read the .newsrc file. * Return list of newsgroup entries. */ static NewsrcGroup * readNewsrc (void) { FILE *nrcFile; char group_name[BUFSIZ]; NewsrcGroup *head, *np, *lnp; /* lnp points to last entry */ lnp = NULL; head = NULL; /* Open it */ if ((nrcFile = fopen(newsrcFile, "r")) == NULL) { fprintf(stderr, "%s: can't open %s\n", progname, newsrcFile); return NULL; } /* Read newsgroup entry */ while (fscanf(nrcFile, "%[^:!]", group_name) == 1) { if (group_name[0] == '\0') break; /* Allocate a new entry */ np = (NewsrcGroup *)xmalloc(sizeof(NewsrcGroup)); np->subscribed = (fgetc(nrcFile) == ':'); /* Parse subscription list */ np->readList = getReadList(nrcFile); np->name = xstrdup(group_name); np->next = NULL; /* Add to list */ if (lnp == NULL) { head = np; } else { lnp->next = np; } lnp = np; } fclose(nrcFile); return head; } /* Write the article numbers for a .newsrc entry. */ static void putReadList (FILE *fd, Range *head) { while (head != NULL) { if (head->lo == head->hi) fprintf(fd, "%d", head->lo); else fprintf(fd, "%d-%d", head->lo, head->hi); head = head->next; if (head != NULL) fputc(',', fd); } fputc('\n', fd); } /* Rewrite the updated .newsrc file */ static int writeNewsrc (NewsrcGroup *head) { char oldFile[FILENAME_MAX]; FILE *nrcFile; NewsrcGroup *np; if (readOnly) return 0; /* Back up old .newsrc file. */ sprintf(oldFile, "%s/newsrc.old", homeDir); remove(oldFile); rename(newsrcFile, oldFile); if ((nrcFile = fopen(newsrcFile, "w")) == NULL) { fprintf(stderr, "%s: can't write %s\n", progname, newsrcFile); return 0; } for (np = head; np != NULL; np = np->next) { fputs(np->name, nrcFile); fputc(np->subscribed ? ':' : '!', nrcFile); fputc(' ', nrcFile); putReadList(nrcFile, np->readList); } fclose(nrcFile); return 1; } /* Get first unread article number. */ static int firstUnread (Range *head) { if (head == NULL) return 1; return head->hi + 1; } /* Determine if the article number has been read */ static int isRead (int num, Range *head) { /* Look through the list */ while (head != NULL) { if (num < head->lo) return 0; if (num >= head->lo && num <= head->hi) return 1; head = head->next; } return 0; } /* Mark article as read. */ static Range * markRead (int num, Range *head) { Range *rp, *trp, *lrp; rp = head; /* If num is much lower than lowest range, or the list is empty, we need new entry */ if (rp == NULL || num < rp->lo - 1) { trp = (Range *)xmalloc(sizeof(Range)); trp->lo = trp->hi = num; trp->next = rp; return trp; } /* lrp remembers last entry in case we need to add a new entry */ lrp = NULL; /* Find appropriate entry for this number */ while (rp != NULL) { /* Have to squeeze one in before this one? */ if (num < rp->lo - 1) { trp = (Range *)xmalloc(sizeof(Range)); trp->lo = trp->hi = num; trp->next = rp; lrp->next = trp; return head; } /* One less than entry's lo? */ if (num == rp->lo - 1) { rp->lo = num; return head; } /* In middle of range, do nothing */ if (num >= rp->lo && num <= rp->hi) return head; /* One too high, must check if we merge with next entry */ if (num == rp->hi + 1) { if (rp->next != NULL && num == rp->next->lo - 1) { trp = rp->next; rp->hi = trp->hi; rp->next = trp->next; free(trp); return head; } else { /* No merge */ rp->hi = num; return head; } } lrp = rp; rp = rp->next; } /* We flew off the end and need a new entry */ trp = (Range *)xmalloc(sizeof(Range)); trp->lo = trp->hi = num; trp->next = NULL; lrp->next = trp; return head; } /* Sanity fixes to the read article number list */ static Range * fixReadList (Range *head, int lo, int hi) { Range *rp1, *rp2, *rp3; /* Check if list is empty. */ if (head == NULL) { /* If lowest available article is 1, then leave read list empty, * otherwise when a new group was started, the first article would * be skipped. */ if (lo == 1) return NULL; /* Make one new entry marking everything up to the lowest available * article as read. */ rp1 = (Range *)xmalloc(sizeof(Range)); rp1->lo = 1; rp1->hi = (lo > 1) ? (lo-1) : 1; rp1->next = NULL; return rp1; } /* If the highest read article is greater than the highest available article, assume the group has been reset */ for (rp1 = head; rp1->next != NULL; rp1 = rp1->next) ; if (rp1->hi > hi) { /* Mark everything as unread */ head->lo = 1; head->hi = (lo > 1) ? (lo-1) : 1; /* Free the rest */ rp2 = head->next; while (rp2 != NULL) { rp3 = rp2->next; free(rp2); rp2 = rp3; } /* If lowest available article is 1, then leave read list empty, * otherwise when group is reset, the first article would be skipped. */ if (lo == 1) { free(head); return NULL; } head->next = NULL; return head; } /* Now walk through the list and eliminate ranges lower than the lowest available article */ rp1 = head; while (rp1 != NULL) { /* If lowest read article of this range is smaller than the lowest available article, all the rest of the ranges are unnecessary */ if (rp1->lo > lo || rp1->hi >= lo) { rp1->lo = 1; if (rp1->hi < lo) rp1->hi = lo - 1; /* Free the rest */ rp2 = head; while (rp2 != rp1) { rp3 = rp2->next; free(rp2); rp2 = rp3; } return rp1; } rp1 = rp1->next; } return head; /* Probably shouldn't get here */ } /* Process an Xref line. */ static void processXref (char *s) { char *c, *p, name[FILENAME_MAX]; int num; NewsrcGroup *np; /* Skip the host field */ c = strtok(s, " \t"); if (c == NULL) return; /* Look through the rest of the fields */ while ((c = strtok(NULL, " \t")) != NULL) { /* Replace : with space. */ if ((p = strchr(c, ':')) != NULL) *p = ' '; if (sscanf(c, "%s %d", name, &num) == 2) { /* Find .newsrc entry for this group */ for (np = nrcList; np != NULL; np = np->next) { if (stricmp(np->name, name) == 0) { /* Mark as read */ np->readList = markRead(num, np->readList); break; } } } } } /* Get the article and write it to the file stream. * Return TRUE if successful. */ static int doArticle (int socket, int artnum, FILE *msgf) { long artSize; unsigned toRead, wasRead; char buf[BUFSIZ], *bufp; char gotXref, killed; /* Get article to temporary file. */ fseek(tmpF, 0L, SEEK_SET); if (killEnabled) { /* Request article head. */ SockPrintf(socket, "HEAD %d\r\n", artnum); if (SockGets(socket, buf, sizeof(buf)) < 0) { return 0; } if (buf[0] == CHAR_FATAL) { /* Fatal error */ fprintf(stderr, "%s\n", buf); exit(EXIT_FAILURE); } if (buf[0] != CHAR_OK) { return 0; } killed = 0; gotXref = 0; /* Get lines of article head. */ while (SockGets(socket, buf, sizeof(buf)) == 0) { bufp = buf; if (buf[0] == '.') { ++bufp; if (buf[1] == '\0') break; } fputs(bufp, tmpF); fputc('\n', tmpF); if (killEnabled && !killed) if (killLine(bufp)) killed = 1; if (doXref && !gotXref && strnicmp(bufp, "Xref: ", 6) == 0) { processXref(bufp+6); gotXref = 1; } } /* Don't process anymore if article was killed. */ if (killed) { printf("%s: article number %d killed\n", progname, artnum); return 0; } /* Put empty line separating head from body. */ fputc('\n', tmpF); /* Retrieve article body. */ if (!nntpArticle(socket, "BODY", artnum, tmpF)) { return 0; } } else { /* Request article. */ SockPrintf(socket, "ARTICLE %d\r\n", artnum); if (SockGets(socket, buf, sizeof(buf)) < 0) { return 0; } if (buf[0] == CHAR_FATAL) { /* Fatal error */ fprintf(stderr, "%s\n", buf); exit(EXIT_FAILURE); } if (buf[0] != CHAR_OK) { return 0; } gotXref = 0; /* Get lines of article head. */ while (SockGets(socket, buf, sizeof(buf)) == 0) { bufp = buf; if (buf[0] == '.') { ++bufp; if (buf[1] == '\0') break; } fputs(bufp, tmpF); fputc('\n', tmpF); if (*bufp == '\0') break; if (doXref && !gotXref && strnicmp(bufp, "Xref: ", 6) == 0) { processXref(bufp+6); gotXref = 1; } } /* Retrieve article body. */ while (SockGets(socket, buf, sizeof(buf)) == 0) { bufp = buf; if (buf[0] == '.') { ++bufp; if (buf[1] == '\0') break; } fputs(bufp, tmpF); fputc('\n', tmpF); } } /* Get article size. */ artSize = ftell(tmpF); if (artSize == 0) { return 0; /* Skip empty articles */ } /* Update packet size. Include size of rnews line. */ byteCount += artSize + 14; /* Write "rnews" line */ fprintf(msgf, "#! rnews %ld\n", artSize); /* Copy article body. */ fseek(tmpF, 0L, SEEK_SET); while (artSize > 0) { toRead = (artSize < sizeof(buf)) ? (unsigned)artSize : sizeof(buf); wasRead = fread(buf, sizeof(char), toRead, tmpF); if (wasRead == 0) { perror("read article"); return 0; } if (fwrite(buf, sizeof(char), wasRead, msgf) != wasRead) { perror("write article"); return 0; } artSize -= wasRead; } return 1; } /* Get articles from the newsgroup. * Return TRUE if successful. */ static int doGroup (int socket, NewsrcGroup *np, int groupCnt) { int lo, hi; int first, i, n; int percent, lastPercent; FILE *msgf; /* select group name from news server */ if (!nntpGroup(socket, np->name, &lo, &hi)) return 0; killEnabled = killGroup(np->name); msgf = openMsgFile(groupCnt, np->name, "un"); /* Fix up the read article number list */ np->readList = fixReadList(np->readList, lo, hi); first = firstUnread(np->readList); n = hi - first + 1; if (n < 0) n = 0; printf("%s: %4d unread article%c in %s\n", progname, n, (n == 1) ? ' ' : 's', np->name); lastPercent = 0; /* Look through unread articles */ for (i = first; i <= hi; ++i) { percent = ((i - first + 1) * 100) / n; if (percent != lastPercent) { printf("%d%%\r", percent); lastPercent = percent; } if (!isRead(i, np->readList)) { /* Mark as read */ np->readList = markRead(i, np->readList); /* Process the article */ doArticle(socket, i, msgf); /* Check if too many blocks already */ if (maxBytes > 0 && byteCount >= maxBytes) { printf("%s: maximum packet size exceeded\n", progname); break; } } } closeMsgFile(); return 1; } /* Get unread articles listed in newsrc from NNTP server. */ int getNews (void) { NewsrcGroup *np; int socket; int groupCnt; /* Read .newsrc file */ if ((nrcList = readNewsrc()) == NULL) return 0; /* Read kill file. */ readKillFile(); /* Open NNTP connection. */ if ((socket = nntpConnect()) < 0) return 0; tmpF = tmpfile(); byteCount = 0; groupCnt = 0; /* For each newsgroup in .newsrc file */ for (np = nrcList; np != NULL; np = np->next) { if (np->subscribed) { doGroup(socket, np, ++groupCnt); if (maxBytes > 0 && byteCount >= maxBytes) break; } } fclose(tmpF); nntpClose(socket); writeNewsrc(nrcList); return 1; } /* Catch up in subscribed newsgroups. */ int catchupNews (int numKeep) { NewsrcGroup *np; Range *head, *rp, *pNext; int socket, lo, hi, n; /* Read .newsrc file */ if ((nrcList = readNewsrc()) == NULL) return 0; /* Open NNTP connection. */ if ((socket = nntpConnect()) < 0) return 0; /* For each newsgroup in .newsrc file */ for (np = nrcList; np != NULL; np = np->next) { if (np->subscribed) { /* select group name from news server */ if (nntpGroup(socket, np->name, &lo, &hi)) { hi -= numKeep; lo = firstUnread(np->readList); if (hi < lo) hi = lo - 1; /* Mark article numbers 1 to hi as read. */ head = np->readList; if (head == NULL) { head = (Range *)xmalloc(sizeof(Range)); head->next = NULL; np->readList = head; } head->lo = 1; head->hi = hi; rp = head->next; head->next = NULL; /* Free rest of list */ while (rp != NULL) { pNext = rp->next; free(rp); rp = pNext; } } } } nntpClose(socket); writeNewsrc(nrcList); return 1; }