521 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* $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, fd;
 | |
| 
 | |
|     sock = openSocket(news->host, "nntp", news->port);
 | |
|     if (sock < 0)
 | |
| 	goto open_err;
 | |
|     news->rf = newInputStream(sock);
 | |
|     if ((fd = dup(sock)) < 0)
 | |
| 	    goto open_err;
 | |
|     news->wf = fdopen(fd, "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 */
 |