From: Pierre Habouzit Date: Sun, 27 May 2007 10:31:22 +0000 (+0200) Subject: merge all nntp code in nntp.c and move it toplevel X-Git-Url: http://git.madism.org/?a=commitdiff_plain;h=546a6855b7d44c415cb397e3a62f6a854132a0dd;p=apps%2Fmadmutt.git merge all nntp code in nntp.c and move it toplevel Signed-off-by: Pierre Habouzit --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fa11d5..bf0e509 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -302,9 +302,6 @@ ADD_SUBDIRECTORY(lib-mime) ADD_SUBDIRECTORY(lib-sys) ADD_SUBDIRECTORY(lib-ui) ADD_SUBDIRECTORY(imap) -IF(WITH_NNTP) - ADD_SUBDIRECTORY(nntp) -ENDIF(WITH_NNTP) ADD_SUBDIRECTORY(lib-mx) ADD_SUBDIRECTORY(lib-lua) @@ -348,13 +345,14 @@ MADMUTT_SOURCES(madmuttsrc madmuttgen main.c ) - -ADD_EXECUTABLE(madmutt ${madmuttsrc}) IF(WITH_NNTP) - TARGET_LINK_LIBRARIES(madmutt mime sys mx lua imap nntp ui lib) -ELSE(WITH_NNTP) - TARGET_LINK_LIBRARIES(madmutt mime sys mx lua imap ui lib) + MADMUTT_SOURCES(madmuttsrc madmuttgen + nntp.c + ) ENDIF(WITH_NNTP) + +ADD_EXECUTABLE(madmutt ${madmuttsrc}) +TARGET_LINK_LIBRARIES(madmutt mime sys mx lua imap ui lib) SET_TARGET_PROPERTIES(madmutt PROPERTIES LINK_FLAGS ${MUTTLIBS}) ADD_EXECUTABLE(madmutt_dotlock dotlock.c) diff --git a/browser.c b/browser.c index f6ebc08..2ced749 100644 --- a/browser.c +++ b/browser.c @@ -27,7 +27,7 @@ #include #ifdef USE_NNTP -#include +#include "nntp.h" #endif static struct mapping_t FolderHelp[] = { diff --git a/browser.h b/browser.h index 91764b6..5139afc 100644 --- a/browser.h +++ b/browser.h @@ -11,7 +11,7 @@ #define _BROWSER_H 1 #ifdef USE_NNTP -#include "nntp/nntp.h" +#include "nntp.h" #endif struct folder_file { diff --git a/compose.c b/compose.c index 031fdab..d607021 100644 --- a/compose.c +++ b/compose.c @@ -33,7 +33,7 @@ #include "remailer.h" #ifdef USE_NNTP -#include +#include "nntp.h" #endif #define CHECK_COUNT \ diff --git a/lib-mx/mx.c b/lib-mx/mx.c index 5f4b8dd..72f1349 100644 --- a/lib-mx/mx.c +++ b/lib-mx/mx.c @@ -32,7 +32,7 @@ #include #include "pop.h" #ifdef USE_NNTP -#include +#include "nntp.h" #endif static mx_t const *mxfmts[] = { diff --git a/lib-ui/complete.c b/lib-ui/complete.c index 511fee0..a06593b 100644 --- a/lib-ui/complete.c +++ b/lib-ui/complete.c @@ -14,7 +14,7 @@ #include "curses.h" #include #ifdef USE_NNTP -#include +#include "nntp.h" #endif /* given a partial pathname, this routine fills in as much of the rest of the diff --git a/lib-ui/curs_main.c b/lib-ui/curs_main.c index 6648b1a..dc78290 100644 --- a/lib-ui/curs_main.c +++ b/lib-ui/curs_main.c @@ -32,7 +32,7 @@ #include #ifdef USE_NNTP -#include +#include "nntp.h" #endif static const char *No_mailbox_is_open = N_("No mailbox is open."); diff --git a/main.c b/main.c index 801966b..f7734fb 100644 --- a/main.c +++ b/main.c @@ -46,7 +46,7 @@ extern int optind; #endif #ifdef USE_NNTP -#include +#include "nntp.h" #endif #ifdef USE_HCACHE diff --git a/nntp/nntp.c b/nntp.c similarity index 58% rename from nntp/nntp.c rename to nntp.c index b39e67a..73c254b 100644 --- a/nntp/nntp.c +++ b/nntp.c @@ -11,6 +11,8 @@ #include +#include + #include #include #include @@ -22,6 +24,1076 @@ #include "buffy.h" #include "crypt.h" +#define NNTP_PORT 119 +#define NNTP_SSL_PORT 563 + +static int nntp_get_cache_all (NNTP_SERVER *); +static int nntp_check_newgroups (NNTP_SERVER *, int); +static void mutt_newsgroup_stat (NNTP_DATA *); +static void nntp_delete_cache (NNTP_DATA *); +static void nntp_delete_data (void *p); + +/* newsrc {{{ */ + +static void nntp_add_to_list(NNTP_SERVER *s, NNTP_DATA *d) +{ + *s->tail = p_new(string_list_t, 1); + (*s->tail)->data = (void *)d; + s->tail = &(*s->tail)->next; +} + +static int nntp_parse_newsrc_line (NNTP_SERVER * news, char *line) +{ + NNTP_DATA *data; + char group[LONG_STRING]; + int x = 1; + char *p = line, *b, *h; + ssize_t len; + + while (*p) { + if (*p++ == ',') + x++; + } + + p = line; + while (*p && (*p != ':' && *p != '!')) + p++; + if (!*p) + return -1; + len = MIN(p + 1 - line, ssizeof(group)); + m_strcpy(group, len, line); + if ((data = (NNTP_DATA *) hash_find (news->newsgroups, group)) == NULL) { + data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data); + nntp_add_to_list (news, data); + } + else + p_delete(&data->entries); + + data->rc = 1; + data->entries = p_new(NEWSRC_ENTRY, x * 2); + data->max = x * 2; + + if (*p == ':') + data->subscribed = 1; + else + data->subscribed = 0; + + p++; + b = p; + x = 0; + while (*b) { + while (*p && *p != ',' && *p != '\n') + p++; + if (*p) { + *p = '\0'; + p++; + } + if ((h = strchr (b, '-'))) { + *h = '\0'; + h++; + data->entries[x].first = atoi (b); + data->entries[x].last = atoi (h); + } + else { + data->entries[x].first = atoi (b); + data->entries[x].last = data->entries[x].first; + } + b = p; + if (data->entries[x].last != 0) + x++; + } + if (x && !data->lastMessage) + data->lastMessage = data->entries[x - 1].last; + data->num = x; + mutt_newsgroup_stat (data); + + return 0; +} + +static int slurp_newsrc (NNTP_SERVER * news) +{ + FILE *fp; + char *buf; + struct stat sb; + + news->stat = stat (news->newsrc, &sb); + news->size = sb.st_size; + news->mtime = sb.st_mtime; + + if ((fp = safe_fopen (news->newsrc, "r")) == NULL) + return -1; + /* hmm, should we use dotlock? */ + if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) { + m_fclose(&fp); + return -1; + } + + buf = p_new(char, sb.st_size + 1); + while (fgets (buf, sb.st_size + 1, fp)) + nntp_parse_newsrc_line (news, buf); + p_delete(&buf); + + mx_unlock_file (news->newsrc, fileno (fp), 0); + m_fclose(&fp); + return 0; +} + +static void nntp_cache_expand (char *dst, const char *src) +{ + snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src); + mutt_expand_path (dst, _POSIX_PATH_MAX); +} + +/* Loads $news_cache_dir/.index into memory, loads newsserver data + * and newsgroup cache names */ +static int nntp_parse_cacheindex (NNTP_SERVER * news) +{ + struct stat st; + char buf[HUGE_STRING], *cp; + char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX]; + FILE *idx; + NNTP_DATA *data; + int l, m, t; + + /* check is server name defined or not */ + if (!news || !news->conn || !news->conn->account.host) + return -1; + unset_option (OPTNEWSCACHE); + if (m_strisempty(NewsCacheDir)) + return 0; + + m_strcpy(dir, sizeof(dir), NewsCacheDir); + mutt_expand_path (dir, sizeof (dir)); + + if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) { + snprintf (buf, sizeof (buf), _("Directory %s not exist. Create it?"), + dir); + if (mutt_yesorno (buf, M_YES) != M_YES + || mkdir (dir, (S_IRWXU + S_IRWXG + S_IRWXO))) { + mutt_error _("Cache directory not created!"); + + return -1; + } + mutt_clear_error (); + } + + set_option (OPTNEWSCACHE); + + p_delete(&news->cache); + snprintf (buf, sizeof (buf), "%s/.index", dir); + if (!(idx = safe_fopen (buf, "a+"))) + return 0; + rewind (idx); + while (fgets (buf, sizeof (buf), idx)) { + buf[m_strlen(buf) - 1] = 0; /* strip ending '\n' */ + if (!m_strncmp(buf, "#: ", 3) && + !m_strcasecmp(buf + 3, news->conn->account.host)) + break; + } + while (fgets (buf, sizeof (buf), idx)) { + cp = buf; + while (*cp && *cp != ' ') + cp++; + if (!*cp) + continue; + cp[0] = 0; + if (!m_strcmp(buf, "#:")) + break; + sscanf (cp + 1, "%s %d %d", file, &l, &m); + if (!m_strcmp(buf, "ALL")) { + news->cache = m_strdup(file); + news->newgroups_time = m; + } + else if (news->newsgroups) { + if ((data = (NNTP_DATA *) hash_find (news->newsgroups, buf)) == NULL) { + data = xmalloc(sizeof(NNTP_DATA) + m_strlen(buf) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, buf); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data); + nntp_add_to_list (news, data); + } + data->cache = m_strdup(file); + t = 0; + if (!data->firstMessage || data->lastMessage < m) + t = 1; + if (!data->firstMessage) + data->firstMessage = l; + if (data->lastMessage < m) + data->lastMessage = m; + data->lastCached = m; + if (t || !data->unread) + mutt_newsgroup_stat (data); + } + } + m_fclose(&idx); + return 0; +} + +const char *nntp_format_str(char *dest, ssize_t destlen, char op, + const char *src, const char *fmt, + const char *ifstr __attribute__((unused)), + const char *elstr __attribute__((unused)), + anytype data __attribute__((unused)), + format_flag flags __attribute__((unused))) +{ + char fn[STRING], tmp[STRING]; + + switch (op) { + case 's': + m_strcpy(fn, sizeof (fn), NewsServer); + m_strtolower(fn); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + } + return (src); +} + +/* nntp_parse_url: given an NNPT URL, return host, port, + * username, password and newsgroup will recognise. */ +static int nntp_parse_url(const char *server, ACCOUNT * act, char *group, + ssize_t group_len) +{ + ciss_url_t url; + char *c; + int ret = -1; + + /* Defaults */ + act->flags = 0; + act->port = NNTP_PORT; + act->type = M_ACCT_TYPE_NNTP; + + c = m_strdup(server); + url_parse_ciss (&url, c); + + if (url.scheme == U_NNTP || url.scheme == U_NNTPS) { + if (url.scheme == U_NNTPS) { + act->has_ssl = 1; + act->port = NNTP_SSL_PORT; + } + + *group = '\0'; + if (url.path) + m_strcpy(group, group_len, url.path); + + ret = mutt_account_fromurl (act, &url); + } + + p_delete(&c); + return ret; +} + +void nntp_expand_path (char *line, ssize_t len, ACCOUNT * act) +{ + ciss_url_t url; + + url.path = m_strdup(line); + mutt_account_tourl (act, &url); + url_ciss_tostring (&url, line, len, 0); + p_delete(&url.path); +} + +/* + * Automatically loads a newsrc into memory, if necessary. + * Checks the size/mtime of a newsrc file, if it doesn't match, load + * again. Hmm, if a system has broken mtimes, this might mean the file + * is reloaded every time, which we'd have to fix. + * + * a newsrc file is a line per newsgroup, with the newsgroup, then a + * ':' denoting subscribed or '!' denoting unsubscribed, then a + * comma separated list of article numbers and ranges. + */ +NNTP_SERVER *mutt_select_newsserver (char *server) +{ + char file[_POSIX_PATH_MAX]; + char *buf, *p; + string_list_t *list; + ACCOUNT act; + NNTP_SERVER *serv; + CONNECTION *conn; + + p_clear(&act, 1); + + if (!server || !*server) { + mutt_error _("No newsserver defined!"); + + return NULL; + } + + buf = p = p_new(char, m_strlen(server) + 10); + if (url_check_scheme (server) == U_UNKNOWN) { + strcpy (buf, "nntp://"); + p = strchr (buf, '\0'); + } + strcpy (p, server); + + if ((nntp_parse_url (buf, &act, file, sizeof (file))) < 0 || *file) { + p_delete(&buf); + mutt_error (_("%s is an invalid newsserver specification!"), server); + return NULL; + } + p_delete(&buf); + + conn = mutt_conn_find (NULL, &act); + if (!conn) + return NULL; + + m_strformat(file, sizeof(file), 0, NewsRc, nntp_format_str, NULL, 0); + mutt_expand_path(file, sizeof(file)); + + serv = (NNTP_SERVER *) conn->data; + if (serv) { + struct stat sb; + + /* externally modified? */ + if (serv->stat != stat (file, &sb) || (!serv->stat && + (serv->size != sb.st_size + || serv->mtime != sb.st_mtime))) { + for (list = serv->list; list; list = list->next) { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data) { + data->subscribed = 0; + data->rc = 0; + data->num = 0; + } + } + slurp_newsrc (serv); + nntp_clear_cacheindex (serv); + } + + if (serv->status == NNTP_BYE) + serv->status = NNTP_NONE; + nntp_check_newgroups (serv, 0); + return serv; + } + + /* New newsserver */ + serv = p_new(NNTP_SERVER, 1); + serv->tail = &serv->list; + serv->conn = conn; + serv->newsrc = m_strdup(file); + serv->newsgroups = hash_new(1009, false); + slurp_newsrc (serv); /* load .newsrc */ + nntp_parse_cacheindex (serv); /* load .index */ + if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0) + nntp_check_newgroups (serv, 1); + else if (nntp_get_active (serv) < 0) { + hash_delete (&serv->newsgroups, nntp_delete_data); + for (list = serv->list; list; list = list->next) + list->data = NULL; + string_list_wipe(&serv->list); + p_delete(&serv->newsrc); + p_delete(&serv->cache); + p_delete(&serv); + return NULL; + } + nntp_clear_cacheindex (serv); + conn->data = (void *) serv; + + return serv; +} + +/* + * full status flags are not supported by nntp, but we can fake some + * of them. This is how: + * Read = a read message number is in the .newsrc + * New = a message is new since we last read this newsgroup + * Old = anything else + * So, Read is marked as such in the newsrc, old is anything that is + * "skipped" in the newsrc, and new is anything not in the newsrc nor + * in the cache. By skipped, I mean before the last unread message + */ +static void nntp_get_status(CONTEXT *ctx, HEADER *h, char *group, int article) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int x; + + if (group) + data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group); + + if (!data) { + return; + } + + for (x = 0; x < data->num; x++) { + if ((article >= data->entries[x].first) && + (article <= data->entries[x].last)) { + /* we cannot use mutt_set_flag() because mx_update_context() + didn't called yet */ + h->read = 1; + return; + } + } + /* If article was not cached yet, it is new! :) */ + if (!data->cache || article > data->lastCached) + return; + /* Old articles are articles which aren't read but an article after them + * has been cached */ + if (option (OPTMARKOLD)) + h->old = 1; +} + +static void mutt_newsgroup_stat (NNTP_DATA * data) +{ + int i, first, last; + + data->unread = 0; + if (data->lastMessage == 0 || data->firstMessage > data->lastMessage) + return; + + data->unread = data->lastMessage - data->firstMessage + 1; + for (i = 0; i < data->num; i++) { + first = data->entries[i].first; + if (first < data->firstMessage) + first = data->firstMessage; + last = data->entries[i].last; + if (last > data->lastMessage) + last = data->lastMessage; + if (first <= last) + data->unread -= last - first + 1; + } +} + +static int puti (char *line, int num) +{ + char *p, s[32]; + + for (p = s; num;) { + *p++ = '0' + num % 10; + num /= 10; + } + while (p > s) + *line++ = *--p, num++; + *line = '\0'; + return num; +} + +static void nntp_create_newsrc_line (NNTP_DATA * data, char **buf, + char **pline, ssize_t * buflen) +{ + char *line = *pline; + ssize_t len = *buflen - (*pline - *buf); + int x, i; + + if (len < LONG_STRING * 10) { + len += *buflen; + *buflen *= 2; + line = *buf; + p_realloc(buf, *buflen); + line = *buf + (*pline - line); + } + strcpy (line, data->group); + len -= m_strlen(line) + 1; + line += m_strlen(line); + *line++ = data->subscribed ? ':' : '!'; + *line++ = ' '; + *line = '\0'; + + for (x = 0; x < data->num; x++) { + if (len < LONG_STRING) { + len += *buflen; + *buflen *= 2; + *pline = line; + line = *buf; + p_realloc(buf, *buflen); + line = *buf + (*pline - line); + } + if (x) { + *line++ = ','; + len--; + } + + i = puti (line, data->entries[x].first); + line += i; + len -= i; + if (data->entries[x].first != data->entries[x].last) { + *line++ = '-'; + len--; + i = puti (line, data->entries[x].last); + line += i; + len -= i; + } + } + *line++ = '\n'; + *line = '\0'; + *pline = line; +} + +static void newsrc_gen_entries (CONTEXT * ctx) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int series, x; + int last = 0, first = 1; + int save_sort = SORT_ORDER; + + if (Sort != SORT_ORDER) { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + if (!data->max) { + data->entries = p_new(NEWSRC_ENTRY, 5); + data->max = 5; + } + + /* + * Set up to fake initial sequence from 1 to the article before the + * first article in our list + */ + data->num = 0; + series = 1; + + for (x = 0; x < ctx->msgcount; x++) { + if (series) { /* search for first unread */ + /* + * We don't actually check sequential order, since we mark + * "missing" entries as read/deleted + */ + last = ctx->hdrs[x]->article_num; + if (last >= data->firstMessage && !ctx->hdrs[x]->deleted && + !ctx->hdrs[x]->read) { + if (data->num >= data->max) { + data->max = data->max * 2; + p_realloc(&data->entries, data->max); + } + data->entries[data->num].first = first; + data->entries[data->num].last = last - 1; + data->num++; + series = 0; + } + } + else { /* search for first read */ + + if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) { + first = last + 1; + series = 1; + } + last = ctx->hdrs[x]->article_num; + } + } + if (series && first <= data->lastLoaded) { + if (data->num >= data->max) { + data->max = data->max * 2; + p_realloc(&data->entries, data->max); + } + data->entries[data->num].first = first; + data->entries[data->num].last = data->lastLoaded; + data->num++; + } + + if (save_sort != Sort) { + Sort = save_sort; + mutt_sort_headers (ctx, 0); + } +} + +static int mutt_update_list_file (char *filename, char *section, + const char *key, char *line) { + FILE *ifp; + FILE *ofp; + char buf[HUGE_STRING]; + char tmpf[_POSIX_PATH_MAX], lnk[_POSIX_PATH_MAX]; + char *c; + int ext = 0, done = 0, r = 0, l = 0; + + /* if file not exist, create it */ + if ((ifp = safe_fopen (filename, "a"))) + m_fclose(&ifp); + if (!(ifp = safe_fopen (filename, "r"))) { + mutt_error (_("Unable to open %s for reading"), filename); + return -1; + } + if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) { + m_fclose(&ifp); + mutt_error (_("Unable to lock %s"), filename); + return -1; + } + + /* use m_tempfile() to get a tempfile in the same + * directory as filename is so that we can follow symlinks + * via rename(2); as dirname(2) may modify its argument, + * temporarily use buf as copy of it + */ + m_strcpy(buf, sizeof(buf), filename); + ofp = m_tempfile(tmpf, sizeof(tmpf), dirname(buf), filename); + if (!ofp) { + m_fclose(&ifp); + mutt_error (_("Unable to open %s for writing"), tmpf); + return -1; + } + + if (section) { + while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) { + r = fputs (buf, ofp); + c = buf; + while (*c && *c != '\n') c++; + c[0] = 0; /* strip EOL */ + if (!strncmp (buf, "#: ", 3) && !m_strcasecmp(buf+3, section)) + done++; + } + if (r != EOF && !done) { + snprintf (buf, sizeof(buf), "#: %s\n", section); + r = fputs (buf, ofp); + } + done = 0; + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) { + if (ext) { + c = buf; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c != '\\') ext = 0; + } else if ((section && !strncmp (buf, "#: ", 3))) { + if (!done && line) { + fputs (line, ofp); + fputc ('\n', ofp); + } + r = fputs (buf, ofp); + done++; + break; + } else if (key && !strncmp (buf, key, strlen(key)) && + (!*key || buf[strlen(key)] == ' ')) { + c = buf; + ext = 0; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c == '\\') ext = 1; + if (!done && line) { + r = fputs (line, ofp); + if (*key) + r = fputc ('\n', ofp); + done++; + } + } else { + r = fputs (buf, ofp); + } + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) + r = fputs (buf, ofp); + + /* If there wasn't a line to replace, put it on the end of the file */ + if (r != EOF && !done && line) { + fputs (line, ofp); + r = fputc ('\n', ofp); + } + mx_unlock_file (filename, fileno (ifp), 0); + m_fclose(&ofp); + m_fclose(&ifp); + if (r == EOF) { + unlink (tmpf); + mutt_error (_("Can't write %s"), tmpf); + return -1; + } + lnk[0] = '\0'; + if ((l = readlink (filename, lnk, sizeof(lnk)-1)) > 0) + lnk[l] = '\0'; + if (rename (tmpf, l > 0 ? lnk : filename) < 0) { + unlink (tmpf); + mutt_error (_("Can't rename %s to %s"), tmpf, l > 0 ? lnk : filename); + return -1; + } + return 0; +} + +int mutt_newsrc_update (NNTP_SERVER * news) +{ + char *buf, *line; + NNTP_DATA *data; + string_list_t *tmp; + int r = -1; + ssize_t len, llen; + + if (!news) + return -1; + llen = len = 10 * LONG_STRING; + line = buf = p_new(char, len); + /* we will generate full newsrc here */ + for (tmp = news->list; tmp; tmp = tmp->next) { + data = (NNTP_DATA *) tmp->data; + if (!data || !data->rc) + continue; + nntp_create_newsrc_line (data, &buf, &line, &llen); + line += m_strlen(line); + } + /* newrc being fully rewritten */ + if (news->newsrc && + (r = mutt_update_list_file(news->newsrc, NULL, "", buf)) == 0) { + struct stat st; + + stat (news->newsrc, &st); + news->size = st.st_size; + news->mtime = st.st_mtime; + } + p_delete(&buf); + return r; +} + +static FILE *mutt_mkname (char *s) +{ + char buf[_POSIX_PATH_MAX], *pc; + int fd; + FILE *fp; + + nntp_cache_expand (buf, s); + if ((fp = safe_fopen (buf, "w"))) + return fp; + + nntp_cache_expand (buf, "cache-XXXXXX"); + pc = buf + m_strlen(buf) - 12; /* positioning to "cache-XXXXXX" */ + if ((fd = mkstemp (buf)) == -1) + return NULL; + strcpy (s, pc); /* generated name */ + return fdopen (fd, "w"); +} + +/* Updates info into .index file: ALL or about selected newsgroup */ +static int nntp_update_cacheindex (NNTP_SERVER * serv, NNTP_DATA * data) +{ + char buf[LONG_STRING]; + char file[_POSIX_PATH_MAX]; + const char *key = "ALL"; + + if (!serv || !serv->conn || !serv->conn->account.host) + return -1; + + if (data && data->group) { + key = data->group; + snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache, + data->firstMessage, data->lastLoaded); + } + else { + m_strcpy(file, sizeof(file), serv->cache); + snprintf (buf, sizeof (buf), "ALL %s 0 %d", file, + (int) serv->newgroups_time); + } + nntp_cache_expand (file, ".index"); + return mutt_update_list_file (file, serv->conn->account.host, key, buf); +} + +/* Remove cache files of unsubscribed newsgroups */ +void nntp_clear_cacheindex (NNTP_SERVER * news) +{ + NNTP_DATA *data; + string_list_t *tmp; + + if (option (OPTSAVEUNSUB) || !news) + return; + + for (tmp = news->list; tmp; tmp = tmp->next) { + data = (NNTP_DATA *) tmp->data; + if (!data || data->subscribed || !data->cache) + continue; + nntp_delete_cache (data); + } + return; +} + +static int nntp_save_cache_index (NNTP_SERVER * news) +{ + char buf[HUGE_STRING]; + char file[_POSIX_PATH_MAX]; + NNTP_DATA *d; + FILE *f; + string_list_t *l; + + if (!news || !news->newsgroups) + return -1; + if (!option (OPTNEWSCACHE)) + return 0; + + if (news->cache) { + nntp_cache_expand (file, news->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else { + m_strcpy(buf, sizeof(buf), news->conn->account.host); + f = mutt_mkname (buf); + news->cache = m_strdup(buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + for (l = news->list; l; l = l->next) { + if ((d = (NNTP_DATA *) l->data) && !d->deleted) { + if (d->desc) + snprintf (buf, sizeof (buf), "%s %d %d %c %s\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n', + d->desc); + else + snprintf (buf, sizeof (buf), "%s %d %d %c\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n'); + if (fputs (buf, f) == EOF) { + m_fclose(&f); + unlink (file); + return -1; + } + } + } + m_fclose(&f); + + if (nntp_update_cacheindex (news, NULL)) { + unlink (file); + return -1; + } + return 0; +} + +static int nntp_save_cache_group (CONTEXT * ctx) +{ + char buf[HUGE_STRING], addr[STRING]; + char file[_POSIX_PATH_MAX]; + FILE *f; + HEADER *h; + struct tm *tm; + int i = 0, save = SORT_ORDER; + int prev = 0; + + if (!option (OPTNEWSCACHE)) + return 0; + if (!ctx || !ctx->data || ctx->magic != M_NNTP) + return -1; + + if (((NNTP_DATA *) ctx->data)->cache) { + nntp_cache_expand (file, ((NNTP_DATA *) ctx->data)->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else { + snprintf (buf, sizeof (buf), "%s-%s", + ((NNTP_DATA *) ctx->data)->nserv->conn->account.host, + ((NNTP_DATA *) ctx->data)->group); + f = mutt_mkname (buf); + ((NNTP_DATA *) ctx->data)->cache = m_strdup(buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + if (Sort != SORT_ORDER) { + save = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + /* Save only $nntp_context messages... */ + ((NNTP_DATA *) ctx->data)->lastCached = 0; + if (NntpContext && ctx->msgcount > NntpContext) + i = ctx->msgcount - NntpContext; + for (; i < ctx->msgcount; i++) { + if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) { + h = ctx->hdrs[i]; + addr[0] = 0; + rfc822_addrcat(addr, sizeof(addr), h->env->from, 0); + tm = gmtime (&h->date_sent); + snprintf (buf, sizeof (buf), + "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t", + h->article_num, h->env->subject, addr, tm->tm_mday, + Months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, + tm->tm_min, tm->tm_sec, h->env->message_id); + fputs (buf, f); + if (h->env->references) + mutt_write_references (h->env->references, f); + snprintf (buf, sizeof (buf), "\t%zd\t%d\tXref: %s\n", + h->content->length, h->lines, NONULL (h->env->xref)); + if (fputs (buf, f) == EOF) { + m_fclose(&f); + unlink (file); + return -1; + } + } + prev = ctx->hdrs[i]->article_num; + } + + if (save != Sort) { + Sort = save; + mutt_sort_headers (ctx, 0); + } + m_fclose(&f); + + if (nntp_update_cacheindex (((NNTP_DATA *) ctx->data)->nserv, + (NNTP_DATA *) ctx->data)) { + unlink (file); + return -1; + } + ((NNTP_DATA *) ctx->data)->lastCached = + ((NNTP_DATA *) ctx->data)->lastLoaded; + return 0; +} + +static void nntp_delete_cache (NNTP_DATA * data) +{ + char buf[_POSIX_PATH_MAX]; + + if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv) + return; + + nntp_cache_expand (buf, data->cache); + unlink (buf); + p_delete(&data->cache); + data->lastCached = 0; + nntp_cache_expand (buf, ".index"); + mutt_update_list_file (buf, data->nserv->conn->account.host, data->group, + NULL); +} + +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER * news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group) + return NULL; + if (!(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) { + data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data); + nntp_add_to_list (news, data); + } + if (!data->subscribed) { + data->subscribed = 1; + data->rc = 1; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER * news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) + return NULL; + if (data->subscribed) { + data->subscribed = 0; + if (!option (OPTSAVEUNSUB)) + data->rc = 0; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER * news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) { + data->entries = p_new(NEWSRC_ENTRY, 5); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->unread = 0; + data->entries[0].last = data->lastMessage; + if (Context && Context->data == data) { + int x; + + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 1); + } + return data; +} + +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER * news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) { + data->entries = p_new(NEWSRC_ENTRY, 5); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->entries[0].last = data->firstMessage - 1; + if (Context && Context->data == data) { + int x; + + data->unread = Context->msgcount; + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 0); + } + else + data->unread = data->lastMessage - data->entries[0].last; + return data; +} + +/* this routine gives the first newsgroup with new messages */ +void nntp_buffy (char* dst, ssize_t dstlen) { + string_list_t *list; + int count = 0; + + /* forward to current group */ + for (list = CurrentNewsSrv->list; list; list = list->next) { + NNTP_DATA *data = (NNTP_DATA *) list->data; + if (data && data->subscribed && data->unread && + Context && Context->magic == M_NNTP && + m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group) == 0) { + list = list->next; + break; + } + } + + *dst = '\0'; + + while (count < 2) { + + if (!list) + list = CurrentNewsSrv->list; + + for (; list; list = list->next) { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data && data->subscribed && data->unread) { + if (Context && Context->magic == M_NNTP && + !m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group)) { + int i, unread = 0; + + for (i = 0; i < Context->msgcount; i++) + if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) + unread++; + if (!unread) + continue; + } + m_strcpy(dst, dstlen, data->group); + break; + } + } + /* done if found */ + if (dst && *dst) + return; + count++; + } + *dst = '\0'; +} + +/* }}} */ +/* nntp protocol {{{ */ + static unsigned int _checked = 0; void nntp_sync_sidebar (NNTP_DATA* data) { @@ -1051,7 +2123,7 @@ static void nntp_free_acache (NNTP_DATA * data) } } -void nntp_delete_data (void *p) +static void nntp_delete_data (void *p) { NNTP_DATA *data = (NNTP_DATA *)p; @@ -1236,7 +2308,7 @@ static int add_group (char *buf, void *serv) return 0; } -int nntp_check_newgroups (NNTP_SERVER * serv, int force) +static int nntp_check_newgroups (NNTP_SERVER * serv, int force) { char buf[LONG_STRING]; NNTP_DATA nntp_data; @@ -1298,7 +2370,7 @@ int nntp_check_newgroups (NNTP_SERVER * serv, int force) } /* Load list of all newsgroups from cache ALL */ -int nntp_get_cache_all (NNTP_SERVER * serv) +static int nntp_get_cache_all (NNTP_SERVER * serv) { char buf[HUGE_STRING]; FILE *f; @@ -1498,3 +2570,5 @@ mx_t const nntp_mx = { nntp_sync_mailbox, NULL, }; + +/* }}} */ diff --git a/nntp/nntp.h b/nntp.h similarity index 82% rename from nntp/nntp.h rename to nntp.h index 18b20fe..c1bde6e 100644 --- a/nntp/nntp.h +++ b/nntp.h @@ -17,9 +17,6 @@ extern mx_t const nntp_mx; -#define NNTP_PORT 119 -#define NNTP_SSL_PORT 563 - /* number of entries in the hash table */ #define NNTP_CACHE_LEN 10 @@ -29,11 +26,6 @@ enum { NNTP_BYE }; -typedef struct { - int first; - int last; -} NEWSRC_ENTRY; - typedef struct { unsigned feat_known : 1; unsigned hasXPAT : 1; @@ -59,6 +51,11 @@ typedef struct { char *path; } NNTP_CACHE; +typedef struct { + int first; + int last; +} NEWSRC_ENTRY; + typedef struct { NEWSRC_ENTRY *entries; int num; /* number of used entries */ @@ -82,18 +79,6 @@ typedef struct { /* internal functions */ int nntp_get_active (NNTP_SERVER *); -int nntp_get_cache_all (NNTP_SERVER *); -int nntp_save_cache_index (NNTP_SERVER *); -int nntp_check_newgroups (NNTP_SERVER *, int); -int nntp_save_cache_group (CONTEXT *); -int nntp_parse_url (const char *, ACCOUNT *, char *, ssize_t); -void newsrc_gen_entries (CONTEXT *); -void nntp_get_status (CONTEXT *, HEADER *, char *, int); -void mutt_newsgroup_stat (NNTP_DATA *); -void nntp_delete_cache (NNTP_DATA *); -void nntp_add_to_list (NNTP_SERVER *, NNTP_DATA *); -void nntp_cache_expand (char *, const char *); -void nntp_delete_data (void *); /* exposed interface */ NNTP_SERVER *mutt_select_newsserver (char *); diff --git a/nntp/CMakeLists.txt b/nntp/CMakeLists.txt deleted file mode 100644 index c43b80a..0000000 --- a/nntp/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -ADD_LIBRARY(nntp - nntp.c - newsrc.c -) diff --git a/nntp/newsrc.c b/nntp/newsrc.c deleted file mode 100644 index c919d86..0000000 --- a/nntp/newsrc.c +++ /dev/null @@ -1,1078 +0,0 @@ -/* - * Copyright notice from original mutt: - * Copyright (C) 1998 Brandon Long - * Copyright (C) 1999 Andrej Gritsenko - * Copyright (C) 2000-2001 Vsevolod Volkov - * - * This file is part of mutt-ng, see http://www.muttng.org/. - * It's licensed under the GNU General Public License, - * please see the file GPL in the top level source directory. - */ - -#include - -#include - -#include -#include -#include - -#include "mutt.h" -#include "sort.h" -#include "nntp.h" - -void nntp_add_to_list(NNTP_SERVER *s, NNTP_DATA *d) -{ - *s->tail = p_new(string_list_t, 1); - (*s->tail)->data = (void *)d; - s->tail = &(*s->tail)->next; -} - -static int nntp_parse_newsrc_line (NNTP_SERVER * news, char *line) -{ - NNTP_DATA *data; - char group[LONG_STRING]; - int x = 1; - char *p = line, *b, *h; - ssize_t len; - - while (*p) { - if (*p++ == ',') - x++; - } - - p = line; - while (*p && (*p != ':' && *p != '!')) - p++; - if (!*p) - return -1; - len = MIN(p + 1 - line, ssizeof(group)); - m_strcpy(group, len, line); - if ((data = (NNTP_DATA *) hash_find (news->newsgroups, group)) == NULL) { - data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1); - data->group = (char *) data + sizeof (NNTP_DATA); - strcpy (data->group, group); - data->nserv = news; - data->deleted = 1; - if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) - hash_resize (news->newsgroups, news->newsgroups->nelem * 2); - hash_insert (news->newsgroups, data->group, data); - nntp_add_to_list (news, data); - } - else - p_delete(&data->entries); - - data->rc = 1; - data->entries = p_new(NEWSRC_ENTRY, x * 2); - data->max = x * 2; - - if (*p == ':') - data->subscribed = 1; - else - data->subscribed = 0; - - p++; - b = p; - x = 0; - while (*b) { - while (*p && *p != ',' && *p != '\n') - p++; - if (*p) { - *p = '\0'; - p++; - } - if ((h = strchr (b, '-'))) { - *h = '\0'; - h++; - data->entries[x].first = atoi (b); - data->entries[x].last = atoi (h); - } - else { - data->entries[x].first = atoi (b); - data->entries[x].last = data->entries[x].first; - } - b = p; - if (data->entries[x].last != 0) - x++; - } - if (x && !data->lastMessage) - data->lastMessage = data->entries[x - 1].last; - data->num = x; - mutt_newsgroup_stat (data); - - return 0; -} - -static int slurp_newsrc (NNTP_SERVER * news) -{ - FILE *fp; - char *buf; - struct stat sb; - - news->stat = stat (news->newsrc, &sb); - news->size = sb.st_size; - news->mtime = sb.st_mtime; - - if ((fp = safe_fopen (news->newsrc, "r")) == NULL) - return -1; - /* hmm, should we use dotlock? */ - if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) { - m_fclose(&fp); - return -1; - } - - buf = p_new(char, sb.st_size + 1); - while (fgets (buf, sb.st_size + 1, fp)) - nntp_parse_newsrc_line (news, buf); - p_delete(&buf); - - mx_unlock_file (news->newsrc, fileno (fp), 0); - m_fclose(&fp); - return 0; -} - -void nntp_cache_expand (char *dst, const char *src) -{ - snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src); - mutt_expand_path (dst, _POSIX_PATH_MAX); -} - -/* Loads $news_cache_dir/.index into memory, loads newsserver data - * and newsgroup cache names */ -static int nntp_parse_cacheindex (NNTP_SERVER * news) -{ - struct stat st; - char buf[HUGE_STRING], *cp; - char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX]; - FILE *idx; - NNTP_DATA *data; - int l, m, t; - - /* check is server name defined or not */ - if (!news || !news->conn || !news->conn->account.host) - return -1; - unset_option (OPTNEWSCACHE); - if (m_strisempty(NewsCacheDir)) - return 0; - - m_strcpy(dir, sizeof(dir), NewsCacheDir); - mutt_expand_path (dir, sizeof (dir)); - - if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) { - snprintf (buf, sizeof (buf), _("Directory %s not exist. Create it?"), - dir); - if (mutt_yesorno (buf, M_YES) != M_YES - || mkdir (dir, (S_IRWXU + S_IRWXG + S_IRWXO))) { - mutt_error _("Cache directory not created!"); - - return -1; - } - mutt_clear_error (); - } - - set_option (OPTNEWSCACHE); - - p_delete(&news->cache); - snprintf (buf, sizeof (buf), "%s/.index", dir); - if (!(idx = safe_fopen (buf, "a+"))) - return 0; - rewind (idx); - while (fgets (buf, sizeof (buf), idx)) { - buf[m_strlen(buf) - 1] = 0; /* strip ending '\n' */ - if (!m_strncmp(buf, "#: ", 3) && - !m_strcasecmp(buf + 3, news->conn->account.host)) - break; - } - while (fgets (buf, sizeof (buf), idx)) { - cp = buf; - while (*cp && *cp != ' ') - cp++; - if (!*cp) - continue; - cp[0] = 0; - if (!m_strcmp(buf, "#:")) - break; - sscanf (cp + 1, "%s %d %d", file, &l, &m); - if (!m_strcmp(buf, "ALL")) { - news->cache = m_strdup(file); - news->newgroups_time = m; - } - else if (news->newsgroups) { - if ((data = (NNTP_DATA *) hash_find (news->newsgroups, buf)) == NULL) { - data = xmalloc(sizeof(NNTP_DATA) + m_strlen(buf) + 1); - data->group = (char *) data + sizeof (NNTP_DATA); - strcpy (data->group, buf); - data->nserv = news; - data->deleted = 1; - if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) - hash_resize (news->newsgroups, news->newsgroups->nelem * 2); - hash_insert (news->newsgroups, data->group, data); - nntp_add_to_list (news, data); - } - data->cache = m_strdup(file); - t = 0; - if (!data->firstMessage || data->lastMessage < m) - t = 1; - if (!data->firstMessage) - data->firstMessage = l; - if (data->lastMessage < m) - data->lastMessage = m; - data->lastCached = m; - if (t || !data->unread) - mutt_newsgroup_stat (data); - } - } - m_fclose(&idx); - return 0; -} - -const char *nntp_format_str(char *dest, ssize_t destlen, char op, - const char *src, const char *fmt, - const char *ifstr __attribute__((unused)), - const char *elstr __attribute__((unused)), - anytype data __attribute__((unused)), - format_flag flags __attribute__((unused))) -{ - char fn[STRING], tmp[STRING]; - - switch (op) { - case 's': - m_strcpy(fn, sizeof (fn), NewsServer); - m_strtolower(fn); - snprintf (tmp, sizeof (tmp), "%%%ss", fmt); - snprintf (dest, destlen, tmp, fn); - break; - } - return (src); -} - -/* nntp_parse_url: given an NNPT URL, return host, port, - * username, password and newsgroup will recognise. */ -int nntp_parse_url (const char *server, ACCOUNT * act, - char *group, ssize_t group_len) -{ - ciss_url_t url; - char *c; - int ret = -1; - - /* Defaults */ - act->flags = 0; - act->port = NNTP_PORT; - act->type = M_ACCT_TYPE_NNTP; - - c = m_strdup(server); - url_parse_ciss (&url, c); - - if (url.scheme == U_NNTP || url.scheme == U_NNTPS) { - if (url.scheme == U_NNTPS) { - act->has_ssl = 1; - act->port = NNTP_SSL_PORT; - } - - *group = '\0'; - if (url.path) - m_strcpy(group, group_len, url.path); - - ret = mutt_account_fromurl (act, &url); - } - - p_delete(&c); - return ret; -} - -void nntp_expand_path (char *line, ssize_t len, ACCOUNT * act) -{ - ciss_url_t url; - - url.path = m_strdup(line); - mutt_account_tourl (act, &url); - url_ciss_tostring (&url, line, len, 0); - p_delete(&url.path); -} - -/* - * Automatically loads a newsrc into memory, if necessary. - * Checks the size/mtime of a newsrc file, if it doesn't match, load - * again. Hmm, if a system has broken mtimes, this might mean the file - * is reloaded every time, which we'd have to fix. - * - * a newsrc file is a line per newsgroup, with the newsgroup, then a - * ':' denoting subscribed or '!' denoting unsubscribed, then a - * comma separated list of article numbers and ranges. - */ -NNTP_SERVER *mutt_select_newsserver (char *server) -{ - char file[_POSIX_PATH_MAX]; - char *buf, *p; - string_list_t *list; - ACCOUNT act; - NNTP_SERVER *serv; - CONNECTION *conn; - - p_clear(&act, 1); - - if (!server || !*server) { - mutt_error _("No newsserver defined!"); - - return NULL; - } - - buf = p = p_new(char, m_strlen(server) + 10); - if (url_check_scheme (server) == U_UNKNOWN) { - strcpy (buf, "nntp://"); - p = strchr (buf, '\0'); - } - strcpy (p, server); - - if ((nntp_parse_url (buf, &act, file, sizeof (file))) < 0 || *file) { - p_delete(&buf); - mutt_error (_("%s is an invalid newsserver specification!"), server); - return NULL; - } - p_delete(&buf); - - conn = mutt_conn_find (NULL, &act); - if (!conn) - return NULL; - - m_strformat(file, sizeof(file), 0, NewsRc, nntp_format_str, NULL, 0); - mutt_expand_path(file, sizeof(file)); - - serv = (NNTP_SERVER *) conn->data; - if (serv) { - struct stat sb; - - /* externally modified? */ - if (serv->stat != stat (file, &sb) || (!serv->stat && - (serv->size != sb.st_size - || serv->mtime != sb.st_mtime))) { - for (list = serv->list; list; list = list->next) { - NNTP_DATA *data = (NNTP_DATA *) list->data; - - if (data) { - data->subscribed = 0; - data->rc = 0; - data->num = 0; - } - } - slurp_newsrc (serv); - nntp_clear_cacheindex (serv); - } - - if (serv->status == NNTP_BYE) - serv->status = NNTP_NONE; - nntp_check_newgroups (serv, 0); - return serv; - } - - /* New newsserver */ - serv = p_new(NNTP_SERVER, 1); - serv->tail = &serv->list; - serv->conn = conn; - serv->newsrc = m_strdup(file); - serv->newsgroups = hash_new(1009, false); - slurp_newsrc (serv); /* load .newsrc */ - nntp_parse_cacheindex (serv); /* load .index */ - if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0) - nntp_check_newgroups (serv, 1); - else if (nntp_get_active (serv) < 0) { - hash_delete (&serv->newsgroups, nntp_delete_data); - for (list = serv->list; list; list = list->next) - list->data = NULL; - string_list_wipe(&serv->list); - p_delete(&serv->newsrc); - p_delete(&serv->cache); - p_delete(&serv); - return NULL; - } - nntp_clear_cacheindex (serv); - conn->data = (void *) serv; - - return serv; -} - -/* - * full status flags are not supported by nntp, but we can fake some - * of them. This is how: - * Read = a read message number is in the .newsrc - * New = a message is new since we last read this newsgroup - * Old = anything else - * So, Read is marked as such in the newsrc, old is anything that is - * "skipped" in the newsrc, and new is anything not in the newsrc nor - * in the cache. By skipped, I mean before the last unread message - */ -void nntp_get_status (CONTEXT * ctx, HEADER * h, char *group, int article) -{ - NNTP_DATA *data = (NNTP_DATA *) ctx->data; - int x; - - if (group) - data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group); - - if (!data) { - return; - } - - for (x = 0; x < data->num; x++) { - if ((article >= data->entries[x].first) && - (article <= data->entries[x].last)) { - /* we cannot use mutt_set_flag() because mx_update_context() - didn't called yet */ - h->read = 1; - return; - } - } - /* If article was not cached yet, it is new! :) */ - if (!data->cache || article > data->lastCached) - return; - /* Old articles are articles which aren't read but an article after them - * has been cached */ - if (option (OPTMARKOLD)) - h->old = 1; -} - -void mutt_newsgroup_stat (NNTP_DATA * data) -{ - int i, first, last; - - data->unread = 0; - if (data->lastMessage == 0 || data->firstMessage > data->lastMessage) - return; - - data->unread = data->lastMessage - data->firstMessage + 1; - for (i = 0; i < data->num; i++) { - first = data->entries[i].first; - if (first < data->firstMessage) - first = data->firstMessage; - last = data->entries[i].last; - if (last > data->lastMessage) - last = data->lastMessage; - if (first <= last) - data->unread -= last - first + 1; - } -} - -static int puti (char *line, int num) -{ - char *p, s[32]; - - for (p = s; num;) { - *p++ = '0' + num % 10; - num /= 10; - } - while (p > s) - *line++ = *--p, num++; - *line = '\0'; - return num; -} - -static void nntp_create_newsrc_line (NNTP_DATA * data, char **buf, - char **pline, ssize_t * buflen) -{ - char *line = *pline; - ssize_t len = *buflen - (*pline - *buf); - int x, i; - - if (len < LONG_STRING * 10) { - len += *buflen; - *buflen *= 2; - line = *buf; - p_realloc(buf, *buflen); - line = *buf + (*pline - line); - } - strcpy (line, data->group); - len -= m_strlen(line) + 1; - line += m_strlen(line); - *line++ = data->subscribed ? ':' : '!'; - *line++ = ' '; - *line = '\0'; - - for (x = 0; x < data->num; x++) { - if (len < LONG_STRING) { - len += *buflen; - *buflen *= 2; - *pline = line; - line = *buf; - p_realloc(buf, *buflen); - line = *buf + (*pline - line); - } - if (x) { - *line++ = ','; - len--; - } - - i = puti (line, data->entries[x].first); - line += i; - len -= i; - if (data->entries[x].first != data->entries[x].last) { - *line++ = '-'; - len--; - i = puti (line, data->entries[x].last); - line += i; - len -= i; - } - } - *line++ = '\n'; - *line = '\0'; - *pline = line; -} - -void newsrc_gen_entries (CONTEXT * ctx) -{ - NNTP_DATA *data = (NNTP_DATA *) ctx->data; - int series, x; - int last = 0, first = 1; - int save_sort = SORT_ORDER; - - if (Sort != SORT_ORDER) { - save_sort = Sort; - Sort = SORT_ORDER; - mutt_sort_headers (ctx, 0); - } - - if (!data->max) { - data->entries = p_new(NEWSRC_ENTRY, 5); - data->max = 5; - } - - /* - * Set up to fake initial sequence from 1 to the article before the - * first article in our list - */ - data->num = 0; - series = 1; - - for (x = 0; x < ctx->msgcount; x++) { - if (series) { /* search for first unread */ - /* - * We don't actually check sequential order, since we mark - * "missing" entries as read/deleted - */ - last = ctx->hdrs[x]->article_num; - if (last >= data->firstMessage && !ctx->hdrs[x]->deleted && - !ctx->hdrs[x]->read) { - if (data->num >= data->max) { - data->max = data->max * 2; - p_realloc(&data->entries, data->max); - } - data->entries[data->num].first = first; - data->entries[data->num].last = last - 1; - data->num++; - series = 0; - } - } - else { /* search for first read */ - - if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) { - first = last + 1; - series = 1; - } - last = ctx->hdrs[x]->article_num; - } - } - if (series && first <= data->lastLoaded) { - if (data->num >= data->max) { - data->max = data->max * 2; - p_realloc(&data->entries, data->max); - } - data->entries[data->num].first = first; - data->entries[data->num].last = data->lastLoaded; - data->num++; - } - - if (save_sort != Sort) { - Sort = save_sort; - mutt_sort_headers (ctx, 0); - } -} - -static int mutt_update_list_file (char *filename, char *section, - const char *key, char *line) { - FILE *ifp; - FILE *ofp; - char buf[HUGE_STRING]; - char tmpf[_POSIX_PATH_MAX], lnk[_POSIX_PATH_MAX]; - char *c; - int ext = 0, done = 0, r = 0, l = 0; - - /* if file not exist, create it */ - if ((ifp = safe_fopen (filename, "a"))) - m_fclose(&ifp); - if (!(ifp = safe_fopen (filename, "r"))) { - mutt_error (_("Unable to open %s for reading"), filename); - return -1; - } - if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) { - m_fclose(&ifp); - mutt_error (_("Unable to lock %s"), filename); - return -1; - } - - /* use m_tempfile() to get a tempfile in the same - * directory as filename is so that we can follow symlinks - * via rename(2); as dirname(2) may modify its argument, - * temporarily use buf as copy of it - */ - m_strcpy(buf, sizeof(buf), filename); - ofp = m_tempfile(tmpf, sizeof(tmpf), dirname(buf), filename); - if (!ofp) { - m_fclose(&ifp); - mutt_error (_("Unable to open %s for writing"), tmpf); - return -1; - } - - if (section) { - while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) { - r = fputs (buf, ofp); - c = buf; - while (*c && *c != '\n') c++; - c[0] = 0; /* strip EOL */ - if (!strncmp (buf, "#: ", 3) && !m_strcasecmp(buf+3, section)) - done++; - } - if (r != EOF && !done) { - snprintf (buf, sizeof(buf), "#: %s\n", section); - r = fputs (buf, ofp); - } - done = 0; - } - - while (r != EOF && fgets (buf, sizeof (buf), ifp)) { - if (ext) { - c = buf; - while (*c && (*c != '\r') && (*c != '\n')) c++; - c--; - if (*c != '\\') ext = 0; - } else if ((section && !strncmp (buf, "#: ", 3))) { - if (!done && line) { - fputs (line, ofp); - fputc ('\n', ofp); - } - r = fputs (buf, ofp); - done++; - break; - } else if (key && !strncmp (buf, key, strlen(key)) && - (!*key || buf[strlen(key)] == ' ')) { - c = buf; - ext = 0; - while (*c && (*c != '\r') && (*c != '\n')) c++; - c--; - if (*c == '\\') ext = 1; - if (!done && line) { - r = fputs (line, ofp); - if (*key) - r = fputc ('\n', ofp); - done++; - } - } else { - r = fputs (buf, ofp); - } - } - - while (r != EOF && fgets (buf, sizeof (buf), ifp)) - r = fputs (buf, ofp); - - /* If there wasn't a line to replace, put it on the end of the file */ - if (r != EOF && !done && line) { - fputs (line, ofp); - r = fputc ('\n', ofp); - } - mx_unlock_file (filename, fileno (ifp), 0); - m_fclose(&ofp); - m_fclose(&ifp); - if (r == EOF) { - unlink (tmpf); - mutt_error (_("Can't write %s"), tmpf); - return -1; - } - lnk[0] = '\0'; - if ((l = readlink (filename, lnk, sizeof(lnk)-1)) > 0) - lnk[l] = '\0'; - if (rename (tmpf, l > 0 ? lnk : filename) < 0) { - unlink (tmpf); - mutt_error (_("Can't rename %s to %s"), tmpf, l > 0 ? lnk : filename); - return -1; - } - return 0; -} - -int mutt_newsrc_update (NNTP_SERVER * news) -{ - char *buf, *line; - NNTP_DATA *data; - string_list_t *tmp; - int r = -1; - ssize_t len, llen; - - if (!news) - return -1; - llen = len = 10 * LONG_STRING; - line = buf = p_new(char, len); - /* we will generate full newsrc here */ - for (tmp = news->list; tmp; tmp = tmp->next) { - data = (NNTP_DATA *) tmp->data; - if (!data || !data->rc) - continue; - nntp_create_newsrc_line (data, &buf, &line, &llen); - line += m_strlen(line); - } - /* newrc being fully rewritten */ - if (news->newsrc && - (r = mutt_update_list_file(news->newsrc, NULL, "", buf)) == 0) { - struct stat st; - - stat (news->newsrc, &st); - news->size = st.st_size; - news->mtime = st.st_mtime; - } - p_delete(&buf); - return r; -} - -static FILE *mutt_mkname (char *s) -{ - char buf[_POSIX_PATH_MAX], *pc; - int fd; - FILE *fp; - - nntp_cache_expand (buf, s); - if ((fp = safe_fopen (buf, "w"))) - return fp; - - nntp_cache_expand (buf, "cache-XXXXXX"); - pc = buf + m_strlen(buf) - 12; /* positioning to "cache-XXXXXX" */ - if ((fd = mkstemp (buf)) == -1) - return NULL; - strcpy (s, pc); /* generated name */ - return fdopen (fd, "w"); -} - -/* Updates info into .index file: ALL or about selected newsgroup */ -static int nntp_update_cacheindex (NNTP_SERVER * serv, NNTP_DATA * data) -{ - char buf[LONG_STRING]; - char file[_POSIX_PATH_MAX]; - const char *key = "ALL"; - - if (!serv || !serv->conn || !serv->conn->account.host) - return -1; - - if (data && data->group) { - key = data->group; - snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache, - data->firstMessage, data->lastLoaded); - } - else { - m_strcpy(file, sizeof(file), serv->cache); - snprintf (buf, sizeof (buf), "ALL %s 0 %d", file, - (int) serv->newgroups_time); - } - nntp_cache_expand (file, ".index"); - return mutt_update_list_file (file, serv->conn->account.host, key, buf); -} - -/* Remove cache files of unsubscribed newsgroups */ -void nntp_clear_cacheindex (NNTP_SERVER * news) -{ - NNTP_DATA *data; - string_list_t *tmp; - - if (option (OPTSAVEUNSUB) || !news) - return; - - for (tmp = news->list; tmp; tmp = tmp->next) { - data = (NNTP_DATA *) tmp->data; - if (!data || data->subscribed || !data->cache) - continue; - nntp_delete_cache (data); - } - return; -} - -int nntp_save_cache_index (NNTP_SERVER * news) -{ - char buf[HUGE_STRING]; - char file[_POSIX_PATH_MAX]; - NNTP_DATA *d; - FILE *f; - string_list_t *l; - - if (!news || !news->newsgroups) - return -1; - if (!option (OPTNEWSCACHE)) - return 0; - - if (news->cache) { - nntp_cache_expand (file, news->cache); - unlink (file); - f = safe_fopen (file, "w"); - } - else { - m_strcpy(buf, sizeof(buf), news->conn->account.host); - f = mutt_mkname (buf); - news->cache = m_strdup(buf); - nntp_cache_expand (file, buf); - } - if (!f) - return -1; - - for (l = news->list; l; l = l->next) { - if ((d = (NNTP_DATA *) l->data) && !d->deleted) { - if (d->desc) - snprintf (buf, sizeof (buf), "%s %d %d %c %s\n", d->group, - d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n', - d->desc); - else - snprintf (buf, sizeof (buf), "%s %d %d %c\n", d->group, - d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n'); - if (fputs (buf, f) == EOF) { - m_fclose(&f); - unlink (file); - return -1; - } - } - } - m_fclose(&f); - - if (nntp_update_cacheindex (news, NULL)) { - unlink (file); - return -1; - } - return 0; -} - -int nntp_save_cache_group (CONTEXT * ctx) -{ - char buf[HUGE_STRING], addr[STRING]; - char file[_POSIX_PATH_MAX]; - FILE *f; - HEADER *h; - struct tm *tm; - int i = 0, save = SORT_ORDER; - int prev = 0; - - if (!option (OPTNEWSCACHE)) - return 0; - if (!ctx || !ctx->data || ctx->magic != M_NNTP) - return -1; - - if (((NNTP_DATA *) ctx->data)->cache) { - nntp_cache_expand (file, ((NNTP_DATA *) ctx->data)->cache); - unlink (file); - f = safe_fopen (file, "w"); - } - else { - snprintf (buf, sizeof (buf), "%s-%s", - ((NNTP_DATA *) ctx->data)->nserv->conn->account.host, - ((NNTP_DATA *) ctx->data)->group); - f = mutt_mkname (buf); - ((NNTP_DATA *) ctx->data)->cache = m_strdup(buf); - nntp_cache_expand (file, buf); - } - if (!f) - return -1; - - if (Sort != SORT_ORDER) { - save = Sort; - Sort = SORT_ORDER; - mutt_sort_headers (ctx, 0); - } - - /* Save only $nntp_context messages... */ - ((NNTP_DATA *) ctx->data)->lastCached = 0; - if (NntpContext && ctx->msgcount > NntpContext) - i = ctx->msgcount - NntpContext; - for (; i < ctx->msgcount; i++) { - if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) { - h = ctx->hdrs[i]; - addr[0] = 0; - rfc822_addrcat(addr, sizeof(addr), h->env->from, 0); - tm = gmtime (&h->date_sent); - snprintf (buf, sizeof (buf), - "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t", - h->article_num, h->env->subject, addr, tm->tm_mday, - Months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, - tm->tm_min, tm->tm_sec, h->env->message_id); - fputs (buf, f); - if (h->env->references) - mutt_write_references (h->env->references, f); - snprintf (buf, sizeof (buf), "\t%zd\t%d\tXref: %s\n", - h->content->length, h->lines, NONULL (h->env->xref)); - if (fputs (buf, f) == EOF) { - m_fclose(&f); - unlink (file); - return -1; - } - } - prev = ctx->hdrs[i]->article_num; - } - - if (save != Sort) { - Sort = save; - mutt_sort_headers (ctx, 0); - } - m_fclose(&f); - - if (nntp_update_cacheindex (((NNTP_DATA *) ctx->data)->nserv, - (NNTP_DATA *) ctx->data)) { - unlink (file); - return -1; - } - ((NNTP_DATA *) ctx->data)->lastCached = - ((NNTP_DATA *) ctx->data)->lastLoaded; - return 0; -} - -void nntp_delete_cache (NNTP_DATA * data) -{ - char buf[_POSIX_PATH_MAX]; - - if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv) - return; - - nntp_cache_expand (buf, data->cache); - unlink (buf); - p_delete(&data->cache); - data->lastCached = 0; - nntp_cache_expand (buf, ".index"); - mutt_update_list_file (buf, data->nserv->conn->account.host, data->group, - NULL); -} - -NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER * news, char *group) -{ - NNTP_DATA *data; - - if (!news || !news->newsgroups || !group || !*group) - return NULL; - if (!(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) { - data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1); - data->group = (char *) data + sizeof (NNTP_DATA); - strcpy (data->group, group); - data->nserv = news; - data->deleted = 1; - if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) - hash_resize (news->newsgroups, news->newsgroups->nelem * 2); - hash_insert (news->newsgroups, data->group, data); - nntp_add_to_list (news, data); - } - if (!data->subscribed) { - data->subscribed = 1; - data->rc = 1; - } - return data; -} - -NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER * news, char *group) -{ - NNTP_DATA *data; - - if (!news || !news->newsgroups || !group || !*group || - !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) - return NULL; - if (data->subscribed) { - data->subscribed = 0; - if (!option (OPTSAVEUNSUB)) - data->rc = 0; - } - return data; -} - -NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER * news, char *group) -{ - NNTP_DATA *data; - - if (!news || !news->newsgroups || !group || !*group || - !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) - return NULL; - if (!data->max) { - data->entries = p_new(NEWSRC_ENTRY, 5); - data->max = 5; - } - data->num = 1; - data->entries[0].first = 1; - data->unread = 0; - data->entries[0].last = data->lastMessage; - if (Context && Context->data == data) { - int x; - - for (x = 0; x < Context->msgcount; x++) - mutt_set_flag (Context, Context->hdrs[x], M_READ, 1); - } - return data; -} - -NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER * news, char *group) -{ - NNTP_DATA *data; - - if (!news || !news->newsgroups || !group || !*group || - !(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) - return NULL; - if (!data->max) { - data->entries = p_new(NEWSRC_ENTRY, 5); - data->max = 5; - } - data->num = 1; - data->entries[0].first = 1; - data->entries[0].last = data->firstMessage - 1; - if (Context && Context->data == data) { - int x; - - data->unread = Context->msgcount; - for (x = 0; x < Context->msgcount; x++) - mutt_set_flag (Context, Context->hdrs[x], M_READ, 0); - } - else - data->unread = data->lastMessage - data->entries[0].last; - return data; -} - -/* this routine gives the first newsgroup with new messages */ -void nntp_buffy (char* dst, ssize_t dstlen) { - string_list_t *list; - int count = 0; - - /* forward to current group */ - for (list = CurrentNewsSrv->list; list; list = list->next) { - NNTP_DATA *data = (NNTP_DATA *) list->data; - if (data && data->subscribed && data->unread && - Context && Context->magic == M_NNTP && - m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group) == 0) { - list = list->next; - break; - } - } - - *dst = '\0'; - - while (count < 2) { - - if (!list) - list = CurrentNewsSrv->list; - - for (; list; list = list->next) { - NNTP_DATA *data = (NNTP_DATA *) list->data; - - if (data && data->subscribed && data->unread) { - if (Context && Context->magic == M_NNTP && - !m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group)) { - int i, unread = 0; - - for (i = 0; i < Context->msgcount; i++) - if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) - unread++; - if (!unread) - continue; - } - m_strcpy(dst, dstlen, data->group); - break; - } - } - /* done if found */ - if (dst && *dst) - return; - count++; - } - *dst = '\0'; -} diff --git a/pager.c b/pager.c index 5880834..f1c9623 100644 --- a/pager.c +++ b/pager.c @@ -892,7 +892,7 @@ fill_buffer (FILE * f, off_t *last_pos, off_t offset, unsigned char *buf, } #ifdef USE_NNTP -#include +#include "nntp.h" #endif static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, diff --git a/send.c b/send.c index ecc938f..f64c776 100644 --- a/send.c +++ b/send.c @@ -24,7 +24,7 @@ #include "attach.h" #ifdef USE_NNTP -#include +#include "nntp.h" #endif #include "remailer.h" diff --git a/sendlib.c b/sendlib.c index 0889c89..d55da0a 100644 --- a/sendlib.c +++ b/sendlib.c @@ -29,7 +29,7 @@ #include "mutt_idna.h" #ifdef USE_NNTP -#include +#include "nntp.h" #endif #ifdef HAVE_SYSEXITS_H