/* $Id: news.c,v 1.17 2003/10/05 18:52:51 ukai Exp $ */ #include "fm.h" #include "myctype.h" #include <stdio.h> #include <stdlib.h> #include <time.h> #include <signal.h> #include <setjmp.h> #ifdef USE_NNTP #define NEWS_ENDLINE(p) \ ((*(p) == '.' && ((p)[1] == '\n' || (p)[1] == '\r' || (p)[1] == '\0')) || \ *(p) == '\n' || *(p) == '\r' || *(p) == '\0') typedef struct _News { char *host; int port; char *mode; InputStream rf; FILE *wf; } News; static News current_news = { NULL, 0, NULL, NULL, NULL }; static JMP_BUF AbortLoading; static MySignalHandler KeyAbort(SIGNAL_ARG) { LONGJMP(AbortLoading, 1); SIGNAL_RETURN; } static Str news_command(News * news, char *cmd, char *arg, int *status) { Str tmp; if (!news->host) return NULL; if (cmd) { if (arg) tmp = Sprintf("%s %s\r\n", cmd, arg); else tmp = Sprintf("%s\r\n", cmd); fwrite(tmp->ptr, sizeof(char), tmp->length, news->wf); fflush(news->wf); } if (!status) return NULL; *status = -1; tmp = StrISgets(news->rf); if (tmp->length) sscanf(tmp->ptr, "%d", status); return tmp; } static void news_close(News * news) { if (!news->host) return; if (news->rf) { IStype(news->rf) &= ~IST_UNCLOSE; ISclose(news->rf); news->rf = NULL; } if (news->wf) { fclose(news->wf); news->wf = NULL; } news->host = NULL; } static int news_open(News * news) { int sock, status; sock = openSocket(news->host, "nntp", news->port); if (sock < 0) goto open_err; news->rf = newInputStream(sock); news->wf = fdopen(dup(sock), "wb"); if (!news->rf || !news->wf) goto open_err; IStype(news->rf) |= IST_UNCLOSE; news_command(news, NULL, NULL, &status); if (status != 200 && status != 201) goto open_err; if (news->mode) { news_command(news, "MODE", news->mode, &status); if (status != 200 && status != 201) goto open_err; } return TRUE; open_err: news_close(news); return FALSE; } static void news_quit(News * news) { news_command(news, "QUIT", NULL, NULL); news_close(news); } static char * name_from_address(char *str, int n) { char *s, *p; int l, space = TRUE; s = allocStr(str, -1); SKIP_BLANKS(s); if (*s == '<' && (p = strchr(s, '>'))) { *p++ = '\0'; SKIP_BLANKS(p); if (*p == '\0') /* <address> */ s++; else /* <address> name ? */ s = p; } else if ((p = strchr(s, '<'))) /* name <address> */ *p = '\0'; else if ((p = strchr(s, '('))) /* address (name) */ s = p; if (*s == '"' && (p = strchr(s + 1, '"'))) { /* "name" */ *p = '\0'; s++; } else if (*s == '(' && (p = strchr(s + 1, ')'))) { /* (name) */ *p = '\0'; s++; } for (p = s, l = 0; *p; p += get_mclen(p)) { if (IS_SPACE(*p)) { if (space) continue; space = TRUE; } else space = FALSE; l += get_mcwidth(p); if (l > n) break; } *p = '\0'; return s; } static char * html_quote_s(char *str) { Str tmp = NULL; char *p, *q; int space = TRUE; for (p = str; *p; p++) { if (IS_SPACE(*p)) { if (space) continue; q = " "; space = TRUE; } else { q = html_quote_char(*p); space = FALSE; } if (q) { if (tmp == NULL) tmp = Strnew_charp_n(str, (int)(p - str)); Strcat_charp(tmp, q); } else { if (tmp) Strcat_char(tmp, *p); } } if (tmp) return tmp->ptr; return str; } static void add_news_message(Str str, int index, char *date, char *name, char *subject, char *mid, char *scheme, char *group) { time_t t; struct tm *tm; name = name_from_address(name, 16); t = mymktime(date); tm = localtime(&t); Strcat(str, Sprintf("<tr valign=top><td>%d<td nowrap>(%02d/%02d)<td nowrap>%s", index, tm->tm_mon + 1, tm->tm_mday, html_quote_s(name))); if (group) Strcat(str, Sprintf("<td><a href=\"%s%s/%d\">%s</a>\n", scheme, group, index, html_quote(subject))); else Strcat(str, Sprintf("<td><a href=\"%s%s\">%s</a>\n", scheme, html_quote(file_quote(mid)), html_quote(subject))); } /* * [News article] * * RFC 1738 * nntp://<host>:<port>/<newsgroup-name>/<article-number> * news:<message-id> * * * Extension * nntp://<host>:<port>/<newsgroup-name>/<message-id> * nntp://<host>:<port>/<message-id> * news:<newsgroup-name>/<article-number> * news:<newsgroup-name>/<message-id> * * [News group] * * RFC 1738 * news:<newsgroup-name> * * * Extension * nntp://<host>:<port>/<newsgroup-name> * nntp://<host>:<port>/<newsgroup-name>/<start-number>-<end-number> * news:<newsgroup-name>/<start-number>-<end-number> * * <message-id> = <unique>@<full_domain_name> */ InputStream openNewsStream(ParsedURL *pu) { char *host, *mode, *group, *p; Str tmp; int port, status; if (pu->file == NULL || *pu->file == '\0') return NULL; if (pu->scheme == SCM_NNTP || pu->scheme == SCM_NNTP_GROUP) host = pu->host; else host = NNTP_server; if (!host || *host == '\0') { if (current_news.host) news_quit(¤t_news); return NULL; } if (pu->scheme != SCM_NNTP && pu->scheme != SCM_NNTP_GROUP && (p = strchr(host, ':'))) { host = allocStr(host, p - host); port = atoi(p + 1); } else port = pu->port; if (NNTP_mode && *NNTP_mode) mode = NNTP_mode; else mode = NULL; if (current_news.host) { if (!strcmp(current_news.host, host) && current_news.port == port) { tmp = news_command(¤t_news, "MODE", mode ? mode : "READER", &status); if (status != 200 && status != 201) news_close(¤t_news); } else news_quit(¤t_news); } if (!current_news.host) { current_news.host = allocStr(host, -1); current_news.port = port; current_news.mode = mode ? allocStr(mode, -1) : NULL; if (!news_open(¤t_news)) return NULL; } if (pu->scheme == SCM_NNTP || pu->scheme == SCM_NEWS) { /* News article */ group = file_unquote(allocStr(pu->file, -1)); p = strchr(group, '/'); if (p == NULL) { /* <message-id> */ if (!strchr(group, '@')) return NULL; p = group; } else { /* <newsgroup>/<message-id or article-number> */ *p++ = '\0'; news_command(¤t_news, "GROUP", group, &status); if (status != 211) return NULL; } if (strchr(p, '@')) /* <message-id> */ news_command(¤t_news, "ARTICLE", Sprintf("<%s>", p)->ptr, &status); else /* <article-number> */ news_command(¤t_news, "ARTICLE", p, &status); if (status != 220) return NULL; return current_news.rf; } return NULL; } #ifdef USE_M17N Str loadNewsgroup(ParsedURL *pu, wc_ces * charset) #else Str loadNewsgroup0(ParsedURL *pu) #endif { volatile Str page; Str tmp; URLFile f; Buffer *buf; char *qgroup, *p, *q, *s, *t, *n; char *volatile scheme, *volatile group, *volatile list; int status, i, first, last; volatile int flag = 0, start = 0, end = 0; MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL; #ifdef USE_M17N wc_ces doc_charset = DocumentCharset, mime_charset; *charset = WC_CES_US_ASCII; #endif if (current_news.host == NULL || !pu->file || *pu->file == '\0') return NULL; group = allocStr(pu->file, -1); if (pu->scheme == SCM_NNTP_GROUP) scheme = "/"; else scheme = "news:"; if ((list = strchr(group, '/'))) { /* <newsgroup>/<start-number>-<end-number> */ *list++ = '\0'; } if (fmInitialized) { message(Sprintf("Reading newsgroup %s...", group)->ptr, 0, 0); refresh(); } qgroup = html_quote(group); group = file_unquote(group); page = Strnew_m_charp("<html>\n<head>\n<base href=\"", parsedURL2Str(pu)->ptr, "\">\n<title>Newsgroup: ", qgroup, "</title>\n</head>\n<body>\n<h1>Newsgroup: ", qgroup, "</h1>\n<hr>\n", NULL); if (SETJMP(AbortLoading) != 0) { news_close(¤t_news); Strcat_charp(page, "</table>\n<p>Transfer Interrupted!\n"); goto news_end; } TRAP_ON; tmp = news_command(¤t_news, "GROUP", group, &status); if (status != 211) goto news_list; if (sscanf(tmp->ptr, "%d %d %d %d", &status, &i, &first, &last) != 4) goto news_list; if (list && *list) { if ((p = strchr(list, '-'))) { *p++ = '\0'; end = atoi(p); } start = atoi(list); if (start > 0) { if (start < first) start = first; if (end <= 0) end = start + MaxNewsMessage - 1; } } if (start <= 0) { start = first; end = last; if (end - start > MaxNewsMessage - 1) start = end - MaxNewsMessage + 1; } page = Sprintf("<html>\n<head>\n<base href=\"%s\">\n\ <title>Newsgroup: %s %d-%d</title>\n\ </head>\n<body>\n<h1>Newsgroup: %s %d-%d</h1>\n<hr>\n", parsedURL2Str(pu)->ptr, qgroup, start, end, qgroup, start, end); if (start > first) { i = start - MaxNewsMessage; if (i < first) i = first; Strcat(page, Sprintf("<a href=\"%s%s/%d-%d\">[%d-%d]</a>\n", scheme, qgroup, i, start - 1, i, start - 1)); } Strcat_charp(page, "<table>\n"); news_command(¤t_news, "XOVER", Sprintf("%d-%d", start, end)->ptr, &status); if (status == 224) { f.scheme = SCM_NEWS; while (1) { tmp = StrISgets(current_news.rf); if (NEWS_ENDLINE(tmp->ptr)) break; if (sscanf(tmp->ptr, "%d", &i) != 1) continue; if (!(s = strchr(tmp->ptr, '\t'))) continue; s++; if (!(n = strchr(s, '\t'))) continue; *n++ = '\0'; if (!(t = strchr(n, '\t'))) continue; *t++ = '\0'; if (!(p = strchr(t, '\t'))) continue; *p++ = '\0'; if (*p == '<') p++; if (!(q = strchr(p, '>')) && !(q = strchr(p, '\t'))) continue; *q = '\0'; tmp = decodeMIME(Strnew_charp(s), &mime_charset); s = convertLine(&f, tmp, HEADER_MODE, mime_charset ? &mime_charset : charset, mime_charset ? mime_charset : doc_charset)->ptr; tmp = decodeMIME(Strnew_charp(n), &mime_charset); n = convertLine(&f, tmp, HEADER_MODE, mime_charset ? &mime_charset : charset, mime_charset ? mime_charset : doc_charset)->ptr; add_news_message(page, i, t, n, s, p, scheme, pu->scheme == SCM_NNTP_GROUP ? qgroup : NULL); } } else { init_stream(&f, SCM_NEWS, current_news.rf); buf = newBuffer(INIT_BUFFER_WIDTH); for (i = start; i <= end && i <= last; i++) { news_command(¤t_news, "HEAD", Sprintf("%d", i)->ptr, &status); if (status != 221) continue; readHeader(&f, buf, FALSE, NULL); if (!(p = checkHeader(buf, "Message-ID:"))) continue; if (*p == '<') p++; if (!(q = strchr(p, '>')) && !(q = strchr(p, '\t'))) *q = '\0'; if (!(s = checkHeader(buf, "Subject:"))) continue; if (!(n = checkHeader(buf, "From:"))) continue; if (!(t = checkHeader(buf, "Date:"))) continue; add_news_message(page, i, t, n, s, p, scheme, pu->scheme == SCM_NNTP_GROUP ? qgroup : NULL); } } Strcat_charp(page, "</table>\n"); if (end < last) { i = end + MaxNewsMessage; if (i > last) i = last; Strcat(page, Sprintf("<a href=\"%s%s/%d-%d\">[%d-%d]</a>\n", scheme, qgroup, end + 1, i, end + 1, i)); } flag = 1; news_list: tmp = Sprintf("ACTIVE %s", group); if (!strchr(group, '*')) Strcat_charp(tmp, ".*"); news_command(¤t_news, "LIST", tmp->ptr, &status); if (status != 215) goto news_end; while (1) { tmp = StrISgets(current_news.rf); if (NEWS_ENDLINE(tmp->ptr)) break; if (flag < 2) { if (flag == 1) Strcat_charp(page, "<hr>\n"); Strcat_charp(page, "<table>\n"); flag = 2; } p = tmp->ptr; for (q = p; *q && !IS_SPACE(*q); q++) ; *(q++) = '\0'; if (sscanf(q, "%d %d", &last, &first) == 2 && last >= first) i = last - first + 1; else i = 0; Strcat(page, Sprintf ("<tr><td align=right>%d<td><a href=\"%s%s\">%s</a>\n", i, scheme, html_quote(file_quote(p)), html_quote(p))); } if (flag == 2) Strcat_charp(page, "</table>\n"); news_end: Strcat_charp(page, "</body>\n</html>\n"); TRAP_OFF; return page; } void closeNews(void) { news_close(¤t_news); } void disconnectNews(void) { news_quit(¤t_news); } #endif /* USE_NNTP */