summaryrefslogtreecommitdiff
path: root/imap/src/c-client/newsrc.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap/src/c-client/newsrc.c')
-rw-r--r--imap/src/c-client/newsrc.c510
1 files changed, 510 insertions, 0 deletions
diff --git a/imap/src/c-client/newsrc.c b/imap/src/c-client/newsrc.c
new file mode 100644
index 00000000..41f1fd24
--- /dev/null
+++ b/imap/src/c-client/newsrc.c
@@ -0,0 +1,510 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Newsrc manipulation routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 12 September 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include "c-client.h"
+#include "newsrc.h"
+
+#ifndef OLDFILESUFFIX
+#define OLDFILESUFFIX ".old"
+#endif
+
+/* Error message
+ * Accepts: message format
+ * additional message string
+ * message level
+ * Returns: NIL, always
+ */
+
+long newsrc_error (char *fmt,char *text,long errflg)
+{
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,fmt,text);
+ MM_LOG (tmp,errflg);
+ return NIL;
+}
+
+
+/* Write error message
+ * Accepts: newsrc name
+ * file designator
+ * file designator
+ * Returns: NIL, always
+ */
+
+long newsrc_write_error (char *name,FILE *f1,FILE *f2)
+{
+ if (f1) fclose (f1); /* close file designators */
+ if (f2) fclose (f2);
+ return newsrc_error ("Error writing to %.80s",name,ERROR);
+}
+
+
+/* Create newsrc file in local form
+ * Accepts: MAIL stream
+ * notification flag
+ * Returns: file designator of newsrc
+ */
+
+FILE *newsrc_create (MAILSTREAM *stream,int notify)
+{
+ char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream);
+ FILE *f = fopen (newsrc,"wb");
+ if (!f) newsrc_error ("Unable to create news state %.80s",newsrc,ERROR);
+ else if (notify) newsrc_error ("Creating news state %.80s",newsrc,WARN);
+ return f;
+}
+
+/* Write new state in newsrc
+ * Accepts: file designator of newsrc
+ * group
+ * new subscription status character
+ * newline convention
+ * Returns: T if successful, NIL otherwise
+ */
+
+long newsrc_newstate (FILE *f,char *group,char state,char *nl)
+{
+ long ret = (f && (fputs (group,f) != EOF) && ((putc (state,f)) != EOF) &&
+ ((putc (' ',f)) != EOF) && (fputs (nl,f) != EOF)) ? LONGT : NIL;
+ if (fclose (f) == EOF) ret = NIL;
+ return ret;
+}
+
+
+/* Write messages in newsrc
+ * Accepts: file designator of newsrc
+ * MAIL stream
+ * message number/newsgroup message map
+ * newline convention
+ * Returns: T if successful, NIL otherwise
+ */
+
+long newsrc_newmessages (FILE *f,MAILSTREAM *stream,char *nl)
+{
+ unsigned long i,j,k;
+ char tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ int c = ' ';
+ if (stream->nmsgs) { /* have any messages? */
+ for (i = 1,j = k = (mail_elt (stream,i)->private.uid > 1) ? 1 : 0;
+ i <= stream->nmsgs; ++i) {
+ /* deleted message? */
+ if ((elt = mail_elt (stream,i))->deleted) {
+ k = elt->private.uid; /* this is the top of the current range */
+ if (!j) j = k; /* if no range in progress, start one */
+ }
+ else if (j) { /* unread message, ending a range */
+ /* calculate end of range */
+ if (k = elt->private.uid - 1) {
+ /* dump range */
+ sprintf (tmp,(j == k) ? "%c%ld" : "%c%ld-%ld",c,j,k);
+ if (fputs (tmp,f) == EOF) return NIL;
+ c = ','; /* need a comma after the first time */
+ }
+ j = 0; /* no more range in progress */
+ }
+ }
+ if (j) { /* dump trailing range */
+ sprintf (tmp,(j == k) ? "%c%ld" : "%c%ld-%ld",c,j,k);
+ if (fputs (tmp,f) == EOF) return NIL;
+ }
+ }
+ /* write trailing newline, return */
+ return (fputs (nl,f) == EOF) ? NIL : LONGT;
+}
+
+/* List subscribed newsgroups
+ * Accepts: MAIL stream
+ * prefix to append name
+ * pattern to search
+ */
+
+void newsrc_lsub (MAILSTREAM *stream,char *pattern)
+{
+ char *s,*t,*lcl,name[MAILTMPLEN];
+ int c = ' ';
+ int showuppers = pattern[strlen (pattern) - 1] == '%';
+ FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb");
+ if (f) { /* got file? */
+ /* remote name? */
+ if (*(lcl = strcpy (name,pattern)) == '{') lcl = strchr (lcl,'}') + 1;
+ if (*lcl == '#') lcl += 6; /* namespace format name? */
+ while (c != EOF) { /* yes, read newsrc */
+ for (s = lcl; (s < (name + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) &&
+ (c != ':') && (c != '!') && (c != '\015') && (c != '\012');
+ *s++ = c);
+ if (c == ':') { /* found a subscribed newsgroup? */
+ *s = '\0'; /* yes, tie off name */
+ /* report if match */
+ if (pmatch_full (name,pattern,'.')) mm_lsub (stream,'.',name,NIL);
+ else while (showuppers && (t = strrchr (lcl,'.'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (name,pattern,'.'))
+ mm_lsub (stream,'.',name,LATT_NOSELECT);
+ }
+ }
+ while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f);
+ }
+ fclose (f);
+ }
+}
+
+/* Update subscription status of newsrc
+ * Accepts: MAIL stream
+ * group
+ * new subscription status character
+ * Returns: T if successful, NIL otherwise
+ */
+
+long newsrc_update (MAILSTREAM *stream,char *group,char state)
+{
+ char tmp[MAILTMPLEN];
+ char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream);
+ long ret = NIL;
+ FILE *f = fopen (newsrc,"r+b");
+ if (f) { /* update existing file */
+ int c = 0;
+ char *s,nl[3];
+ long pos = 0;
+ nl[0] = nl[1] = nl[2]='\0'; /* no newline known yet */
+ do { /* read newsrc */
+ for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) &&
+ (c != ':') && (c != '!') && (c != '\015') && (c != '\012');
+ *s++ = c) pos = ftell (f);
+ *s = '\0'; /* tie off name */
+ /* found the newsgroup? */
+ if (((c == ':') || (c == '!')) && !strcmp (tmp,group)) {
+ if (c == state) { /* already at that state? */
+ if (c == ':') newsrc_error("Already subscribed to %.80s",group,WARN);
+ ret = LONGT; /* noop the update */
+ }
+ /* write the character */
+ else if (!fseek (f,pos,0)) ret = ((putc (state,f)) == EOF) ? NIL:LONGT;
+ if (fclose (f) == EOF) ret = NIL;
+ f = NIL; /* done with file */
+ break;
+ }
+ /* gobble remainder of this line */
+ while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f);
+ /* need to know about newlines and found it? */
+ if (!nl[0] && ((c == '\015') || (c == '\012')) && ((nl[0]=c) == '\015')){
+ /* sniff and see if an LF */
+ if ((c = getc (f)) == '\012') nl[1] = c;
+ else ungetc (c,f); /* nope, push it back */
+ }
+ } while (c != EOF);
+
+ if (f) { /* still haven't written it yet? */
+ if (nl[0]) { /* know its newline convention? */
+ fseek (f,0L,2); /* yes, seek to end of file */
+ ret = newsrc_newstate (f,group,state,nl);
+ }
+ else { /* can't find a newline convention */
+ fclose (f); /* punt the file */
+ /* can't win if read something */
+ if (pos) newsrc_error ("Unknown newline convention in %.80s",
+ newsrc,ERROR);
+ /* file must have been empty, rewrite it */
+ else ret = newsrc_newstate(newsrc_create(stream,NIL),group,state,"\n");
+ }
+ }
+ }
+ /* create new file */
+ else ret = newsrc_newstate (newsrc_create (stream,T),group,state,"\n");
+ return ret; /* return with update status */
+}
+
+/* Update newsgroup status in stream
+ * Accepts: newsgroup name
+ * MAIL stream
+ * Returns: number of recent messages
+ */
+
+long newsrc_read (char *group,MAILSTREAM *stream)
+{
+ int c = 0;
+ char *s,tmp[MAILTMPLEN];
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ unsigned long m = 1,recent = 0,unseen = 0;
+ FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb");
+ if (f) do { /* read newsrc */
+ for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) &&
+ (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c);
+ *s = '\0'; /* tie off name */
+ if ((c==':') || (c=='!')) { /* found newsgroup? */
+ if (strcmp (tmp,group)) /* group name match? */
+ while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f);
+ else { /* yes, skip leading whitespace */
+ while ((c = getc (f)) == ' ');
+ /* only if unprocessed messages */
+ if (stream->nmsgs) while (f && (m <= stream->nmsgs)) {
+ /* collect a number */
+ if (isdigit (c)) { /* better have a number */
+ for (i = 0,j = 0; isdigit (c); c = getc (f)) i = i*10 + (c-'0');
+ if (c == '-') for (c = getc (f); isdigit (c); c = getc (f))
+ j = j*10 +(c-'0');/* collect second value if range */
+ if (!unseen && (mail_elt (stream,m)->private.uid < i)) unseen = m;
+ /* skip messages before first value */
+ while ((m <= stream->nmsgs) &&
+ ((elt = mail_elt (stream,m))->private.uid < i) && m++)
+ elt->valid = T;
+ /* do all messages in range */
+ while ((m <= stream->nmsgs) && (elt = mail_elt (stream,m)) &&
+ (j ? ((elt->private.uid >= i) && (elt->private.uid <= j)) :
+ (elt->private.uid == i)) && m++)
+ elt->valid = elt->deleted = T;
+ }
+
+ switch (c) { /* what is the delimiter? */
+ case ',': /* more to come */
+ c = getc (f); /* get first character of number */
+ break;
+ default: /* bogus character */
+ sprintf (tmp,"Bogus character 0x%x in news state",(unsigned int)c);
+ MM_LOG (tmp,ERROR);
+ case EOF: case '\015': case '\012':
+ fclose (f); /* all done - close the file */
+ f = NIL;
+ break;
+ }
+ }
+ else { /* empty newsgroup */
+ while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f);
+ fclose (f); /* all done - close the file */
+ f = NIL;
+ }
+ }
+ }
+ } while (f && (c != EOF)); /* until file closed or EOF */
+ if (f) { /* still have file open? */
+ sprintf (tmp,"No state for newsgroup %.80s found, reading as new",group);
+ MM_LOG (tmp,WARN);
+ fclose (f); /* close the file */
+ }
+ if (m <= stream->nmsgs) { /* any messages beyond newsrc range? */
+ if (!unseen) unseen = m; /* then this must be the first unseen one */
+ do {
+ elt = mail_elt (stream,m++);
+ elt->valid = elt->recent = T;
+ ++recent; /* count another recent message */
+ }
+ while (m <= stream->nmsgs);
+ }
+ if (unseen) { /* report first unseen message */
+ sprintf (tmp,"[UNSEEN] %lu is first unseen message in %.80s",unseen,group);
+ MM_NOTIFY (stream,tmp,(long) NIL);
+ }
+ return recent;
+}
+
+/* Update newsgroup entry in newsrc
+ * Accepts: newsgroup name
+ * MAIL stream
+ * Returns: T if successful, NIL otherwise
+ */
+
+long newsrc_write (char *group,MAILSTREAM *stream)
+{
+ long ret = NIL;
+ int c = 0,d = EOF;
+ char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream);
+ char *s,tmp[MAILTMPLEN],backup[MAILTMPLEN],nl[3];
+ FILE *f,*bf;
+ nl[0] = nl[1] = nl[2] = '\0'; /* no newline known yet */
+ if (f = fopen (newsrc,"rb")) {/* have existing newsrc file? */
+ if (!(bf = fopen ((strcat (strcpy (backup,newsrc),OLDFILESUFFIX)),"wb"))) {
+ fclose (f); /* punt input file */
+ return newsrc_error("Can't create backup news state %.80s",backup,ERROR);
+ }
+ /* copy to backup file */
+ while ((c = getc (f)) != EOF) {
+ /* need to know about newlines and found it? */
+ if (!nl[0] && ((c == '\015') || (c == '\012')) && ((nl[0]=c) == '\015')){
+ /* sniff and see if an LF */
+ if ((c = getc (f)) == '\012') nl[1] = c;
+ ungetc (c,f); /* push it back */
+ }
+ /* write to backup file */
+ if ((d = putc (c,bf)) == EOF) {
+ fclose (f); /* punt input file */
+ return newsrc_error ("Error writing backup news state %.80s",
+ newsrc,ERROR);
+ }
+ }
+ fclose (f); /* close existing file */
+ if (fclose (bf) == EOF) /* and backup file */
+ return newsrc_error ("Error closing backup news state %.80s",
+ newsrc,ERROR);
+ if (d == EOF) { /* open for write if empty file */
+ if (f = newsrc_create (stream,NIL)) bf = NIL;
+ else return NIL;
+ }
+ else if (!nl[0]) /* make sure newlines valid */
+ return newsrc_error ("Unknown newline convention in %.80s",newsrc,ERROR);
+ /* now read backup file */
+ else if (!(bf = fopen (backup,"rb")))
+ return newsrc_error ("Error reading backup news state %.80s",
+ backup,ERROR);
+ /* open newsrc for writing */
+ else if (!(f = fopen (newsrc,"wb"))) {
+ fclose (bf); /* punt backup */
+ return newsrc_error ("Can't rewrite news state %.80s",newsrc,ERROR);
+ }
+ }
+ else { /* create new newsrc file */
+ if (f = newsrc_create (stream,T)) bf = NIL;
+ else return NIL; /* can't create newsrc */
+ }
+
+ while (bf) { /* read newsrc */
+ for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (bf)) != EOF) &&
+ (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c);
+ *s = '\0'; /* tie off name */
+ /* saw correct end of group delimiter? */
+ if (tmp[0] && ((c == ':') || (c == '!'))) {
+ /* yes, write newsgroup name and delimiter */
+ if ((tmp[0] && (fputs (tmp,f) == EOF)) || ((putc (c,f)) == EOF))
+ return newsrc_write_error (newsrc,bf,f);
+ if (!strcmp (tmp,group)) {/* found correct group? */
+ /* yes, write new status */
+ if (!newsrc_newmessages (f,stream,nl[0] ? nl : "\n"))
+ return newsrc_write_error (newsrc,bf,f);
+ /* skip past old data */
+ while (((c = getc (bf)) != EOF) && (c != '\015') && (c != '\012'));
+ /* skip past newline */
+ while ((c == '\015') || (c == '\012')) c = getc (bf);
+ while (c != EOF) { /* copy remainder of file */
+ if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f);
+ c = getc (bf); /* get next character */
+ }
+ /* done with file */
+ if (fclose (f) == EOF) return newsrc_write_error (newsrc,bf,NIL);
+ f = NIL;
+ }
+ /* copy remainder of line */
+ else while (((c = getc (bf)) != EOF) && (c != '\015') && (c != '\012'))
+ if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f);
+ if (c == '\015') { /* write CR if seen */
+ if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f);
+ /* sniff to see if LF */
+ if (((c = getc (bf)) != EOF) && (c != '\012')) ungetc (c,bf);
+ }
+ /* write LF if seen */
+ if ((c == '\012') && (putc (c,f) == EOF))
+ return newsrc_write_error (newsrc,bf,f);
+ }
+ if (c == EOF) { /* hit end of file? */
+ fclose (bf); /* yup, close the file */
+ bf = NIL;
+ }
+ }
+ if (f) { /* still have newsrc file open? */
+ ret = ((fputs (group,f) != EOF) && ((putc (':',f)) != EOF) &&
+ newsrc_newmessages (f,stream,nl[0] ? nl : "\n")) ? LONGT : NIL;
+ if (fclose (f) == EOF) ret = newsrc_write_error (newsrc,NIL,NIL);
+ }
+ else ret = LONGT;
+ return ret;
+}
+
+/* Get newsgroup state as text stream
+ * Accepts: MAIL stream
+ * newsgroup name
+ * Returns: string containing newsgroup state, or NIL if not found
+ */
+
+char *newsrc_state (MAILSTREAM *stream,char *group)
+{
+ int c = 0;
+ char *s,tmp[MAILTMPLEN];
+ long pos;
+ size_t size;
+ FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb");
+ if (f) do { /* read newsrc */
+ for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) &&
+ (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c);
+ *s = '\0'; /* tie off name */
+ if ((c==':') || (c=='!')) { /* found newsgroup? */
+ if (strcmp (tmp,group)) /* group name match? */
+ while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f);
+ else { /* yes, skip leading whitespace */
+ do pos = ftell (f);
+ while ((c = getc (f)) == ' ');
+ /* count characters in state */
+ for (size = 0; (c != '\015') && (c != '\012') && (c != EOF); size++)
+ c = getc (f);
+ /* now copy it */
+ s = (char *) fs_get (size + 1);
+ fseek (f,pos,SEEK_SET);
+ fread (s,(size_t) 1,size,f);
+ s[size] = '\0'; /* tie off string */
+ fclose (f); /* all done - close the file */
+ return s;
+ }
+ }
+ } while (f && (c != EOF)); /* until file closed or EOF */
+ sprintf (tmp,"No state for newsgroup %.80s found",group);
+ MM_LOG (tmp,WARN);
+ if (f) fclose (f); /* close the file */
+ return NIL; /* not found return */
+}
+
+/* Check UID in newsgroup state
+ * Accepts: newsgroup state string
+ * uid
+ * returned recent count
+ * returned unseen count
+ */
+
+void newsrc_check_uid (unsigned char *state,unsigned long uid,
+ unsigned long *recent,unsigned long *unseen)
+{
+ unsigned long i,j;
+ while (*state) { /* until run out of state string */
+ /* collect a number */
+ for (i = 0; isdigit (*state); i = i*10 + (*state++ - '0'));
+ if (*state != '-') j = i; /* coerce single mesage into range */
+ else { /* have a range */
+ for (j = 0; isdigit (*++state); j = j*10 + (*state - '0'));
+ if (!j) j = i; /* guard against -0 */
+ if (j < i) return; /* bogon if end less than start */
+ }
+ if (*state == ',') state++; /* skip past comma */
+ else if (*state) return; /* otherwise it's a bogon */
+ if (uid <= j) { /* covered by upper bound? */
+ if (uid < i) ++*unseen; /* unseen if not covered by lower bound */
+ return; /* don't need to look further */
+ }
+ }
+ ++*unseen; /* not found in any range, message is unseen */
+ ++*recent; /* and recent */
+}