1 /* $Id: news.c,v 1.17 2003/10/05 18:52:51 ukai Exp $ */
2 #include "fm.h"
3 #include "myctype.h"
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <time.h>
7 #include <signal.h>
8 #include <setjmp.h>
9 
10 #ifdef USE_NNTP
11 
12 #define NEWS_ENDLINE(p) \
13     ((*(p) == '.' && ((p)[1] == '\n' || (p)[1] == '\r' || (p)[1] == '\0')) || \
14     *(p) == '\n' || *(p) == '\r' || *(p) == '\0')
15 
16 typedef struct _News {
17     char *host;
18     int port;
19     char *mode;
20     InputStream rf;
21     FILE *wf;
22 } News;
23 
24 static News current_news = { NULL, 0, NULL, NULL, NULL };
25 
26 static JMP_BUF AbortLoading;
27 
28 static MySignalHandler
KeyAbort(SIGNAL_ARG)29 KeyAbort(SIGNAL_ARG)
30 {
31     LONGJMP(AbortLoading, 1);
32     SIGNAL_RETURN;
33 }
34 
35 static Str
news_command(News * news,char * cmd,char * arg,int * status)36 news_command(News * news, char *cmd, char *arg, int *status)
37 {
38     Str tmp;
39 
40     if (!news->host)
41 	return NULL;
42     if (cmd) {
43 	if (arg)
44 	    tmp = Sprintf("%s %s\r\n", cmd, arg);
45 	else
46 	    tmp = Sprintf("%s\r\n", cmd);
47 	fwrite(tmp->ptr, sizeof(char), tmp->length, news->wf);
48 	fflush(news->wf);
49     }
50     if (!status)
51 	return NULL;
52     *status = -1;
53     tmp = StrISgets(news->rf);
54     if (tmp->length)
55 	sscanf(tmp->ptr, "%d", status);
56     return tmp;
57 }
58 
59 static void
news_close(News * news)60 news_close(News * news)
61 {
62     if (!news->host)
63 	return;
64     if (news->rf) {
65 	IStype(news->rf) &= ~IST_UNCLOSE;
66 	ISclose(news->rf);
67 	news->rf = NULL;
68     }
69     if (news->wf) {
70 	fclose(news->wf);
71 	news->wf = NULL;
72     }
73     news->host = NULL;
74 }
75 
76 static int
news_open(News * news)77 news_open(News * news)
78 {
79     int sock, status, fd;
80 
81     sock = openSocket(news->host, "nntp", news->port);
82     if (sock < 0)
83 	goto open_err;
84     news->rf = newInputStream(sock);
85     if ((fd = dup(sock)) < 0)
86 	    goto open_err;
87     news->wf = fdopen(fd, "wb");
88     if (!news->rf || !news->wf)
89 	goto open_err;
90     IStype(news->rf) |= IST_UNCLOSE;
91     news_command(news, NULL, NULL, &status);
92     if (status != 200 && status != 201)
93 	goto open_err;
94     if (news->mode) {
95 	news_command(news, "MODE", news->mode, &status);
96 	if (status != 200 && status != 201)
97 	    goto open_err;
98     }
99     return TRUE;
100   open_err:
101     news_close(news);
102     return FALSE;
103 }
104 
105 static void
news_quit(News * news)106 news_quit(News * news)
107 {
108     news_command(news, "QUIT", NULL, NULL);
109     news_close(news);
110 }
111 
112 static char *
name_from_address(char * str,int n)113 name_from_address(char *str, int n)
114 {
115     char *s, *p;
116     int l, space = TRUE;
117 
118     s = allocStr(str, -1);
119     SKIP_BLANKS(s);
120     if (*s == '<' && (p = strchr(s, '>'))) {
121 	*p++ = '\0';
122 	SKIP_BLANKS(p);
123 	if (*p == '\0')		/* <address> */
124 	    s++;
125 	else			/* <address> name ? */
126 	    s = p;
127     }
128     else if ((p = strchr(s, '<')))	/* name <address> */
129 	*p = '\0';
130     else if ((p = strchr(s, '(')))	/* address (name) */
131 	s = p;
132     if (*s == '"' && (p = strchr(s + 1, '"'))) {	/* "name" */
133 	*p = '\0';
134 	s++;
135     }
136     else if (*s == '(' && (p = strchr(s + 1, ')'))) {	/* (name) */
137 	*p = '\0';
138 	s++;
139     }
140     for (p = s, l = 0; *p; p += get_mclen(p)) {
141 	if (IS_SPACE(*p)) {
142 	    if (space)
143 		continue;
144 	    space = TRUE;
145 	}
146 	else
147 	    space = FALSE;
148 	l += get_mcwidth(p);
149 	if (l > n)
150 	    break;
151     }
152     *p = '\0';
153     return s;
154 }
155 
156 static char *
html_quote_s(char * str)157 html_quote_s(char *str)
158 {
159     Str tmp = NULL;
160     char *p, *q;
161     int space = TRUE;
162 
163     for (p = str; *p; p++) {
164 	if (IS_SPACE(*p)) {
165 	    if (space)
166 		continue;
167 	    q = "&nbsp;";
168 	    space = TRUE;
169 	}
170 	else {
171 	    q = html_quote_char(*p);
172 	    space = FALSE;
173 	}
174 	if (q) {
175 	    if (tmp == NULL)
176 		tmp = Strnew_charp_n(str, (int)(p - str));
177 	    Strcat_charp(tmp, q);
178 	}
179 	else {
180 	    if (tmp)
181 		Strcat_char(tmp, *p);
182 	}
183     }
184     if (tmp)
185 	return tmp->ptr;
186     return str;
187 }
188 
189 static void
add_news_message(Str str,int index,char * date,char * name,char * subject,char * mid,char * scheme,char * group)190 add_news_message(Str str, int index, char *date, char *name, char *subject,
191 		 char *mid, char *scheme, char *group)
192 {
193     time_t t;
194     struct tm *tm;
195 
196     name = name_from_address(name, 16);
197     t = mymktime(date);
198     tm = localtime(&t);
199     Strcat(str,
200 	   Sprintf("<tr valign=top><td>%d<td nowrap>(%02d/%02d)<td nowrap>%s",
201 		   index, tm->tm_mon + 1, tm->tm_mday, html_quote_s(name)));
202     if (group)
203 	Strcat(str, Sprintf("<td><a href=\"%s%s/%d\">%s</a>\n", scheme, group,
204 			    index, html_quote(subject)));
205     else
206 	Strcat(str, Sprintf("<td><a href=\"%s%s\">%s</a>\n", scheme,
207 			    html_quote(file_quote(mid)), html_quote(subject)));
208 }
209 
210 /*
211  * [News article]
212  *  * RFC 1738
213  *    nntp://<host>:<port>/<newsgroup-name>/<article-number>
214  *    news:<message-id>
215  *
216  *  * Extension
217  *    nntp://<host>:<port>/<newsgroup-name>/<message-id>
218  *    nntp://<host>:<port>/<message-id>
219  *    news:<newsgroup-name>/<article-number>
220  *    news:<newsgroup-name>/<message-id>
221  *
222  * [News group]
223  *  * RFC 1738
224  *    news:<newsgroup-name>
225  *
226  *  * Extension
227  *    nntp://<host>:<port>/<newsgroup-name>
228  *    nntp://<host>:<port>/<newsgroup-name>/<start-number>-<end-number>
229  *    news:<newsgroup-name>/<start-number>-<end-number>
230  *
231  * <message-id> = <unique>@<full_domain_name>
232  */
233 
234 InputStream
openNewsStream(ParsedURL * pu)235 openNewsStream(ParsedURL *pu)
236 {
237     char *host, *mode, *group, *p;
238     Str tmp;
239     int port, status;
240 
241     if (pu->file == NULL || *pu->file == '\0')
242 	return NULL;
243     if (pu->scheme == SCM_NNTP || pu->scheme == SCM_NNTP_GROUP)
244 	host = pu->host;
245     else
246 	host = NNTP_server;
247     if (!host || *host == '\0') {
248 	if (current_news.host)
249 	    news_quit(&current_news);
250 	return NULL;
251     }
252     if (pu->scheme != SCM_NNTP && pu->scheme != SCM_NNTP_GROUP &&
253 	(p = strchr(host, ':'))) {
254 	host = allocStr(host, p - host);
255 	port = atoi(p + 1);
256     }
257     else
258 	port = pu->port;
259     if (NNTP_mode && *NNTP_mode)
260 	mode = NNTP_mode;
261     else
262 	mode = NULL;
263     if (current_news.host) {
264 	if (!strcmp(current_news.host, host) && current_news.port == port) {
265 	    tmp = news_command(&current_news, "MODE", mode ? mode : "READER",
266 			       &status);
267 	    if (status != 200 && status != 201)
268 		news_close(&current_news);
269 	}
270 	else
271 	    news_quit(&current_news);
272     }
273     if (!current_news.host) {
274 	current_news.host = allocStr(host, -1);
275 	current_news.port = port;
276 	current_news.mode = mode ? allocStr(mode, -1) : NULL;
277 	if (!news_open(&current_news))
278 	    return NULL;
279     }
280     if (pu->scheme == SCM_NNTP || pu->scheme == SCM_NEWS) {
281 	/* News article */
282 	group = file_unquote(allocStr(pu->file, -1));
283 	p = strchr(group, '/');
284 	if (p == NULL) {	/* <message-id> */
285 	    if (!strchr(group, '@'))
286 		return NULL;
287 	    p = group;
288 	}
289 	else {			/* <newsgroup>/<message-id or article-number> */
290 	    *p++ = '\0';
291 	    news_command(&current_news, "GROUP", group, &status);
292 	    if (status != 211)
293 		return NULL;
294 	}
295 	if (strchr(p, '@'))	/* <message-id> */
296 	    news_command(&current_news, "ARTICLE", Sprintf("<%s>", p)->ptr,
297 			 &status);
298 	else			/* <article-number> */
299 	    news_command(&current_news, "ARTICLE", p, &status);
300 	if (status != 220)
301 	    return NULL;
302 	return current_news.rf;
303     }
304     return NULL;
305 }
306 
307 
308 #ifdef USE_M17N
309 Str
loadNewsgroup(ParsedURL * pu,wc_ces * charset)310 loadNewsgroup(ParsedURL *pu, wc_ces * charset)
311 #else
312 Str
313 loadNewsgroup0(ParsedURL *pu)
314 #endif
315 {
316     volatile Str page;
317     Str tmp;
318     URLFile f;
319     Buffer *buf;
320     char *qgroup, *p, *q, *s, *t, *n;
321     char *volatile scheme, *volatile group, *volatile list;
322     int status, i, first, last;
323     volatile int flag = 0, start = 0, end = 0;
324     MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
325 #ifdef USE_M17N
326     wc_ces doc_charset = DocumentCharset, mime_charset;
327 
328     *charset = WC_CES_US_ASCII;
329 #endif
330     if (current_news.host == NULL || !pu->file || *pu->file == '\0')
331 	return NULL;
332     group = allocStr(pu->file, -1);
333     if (pu->scheme == SCM_NNTP_GROUP)
334 	scheme = "/";
335     else
336 	scheme = "news:";
337     if ((list = strchr(group, '/'))) {
338 	/* <newsgroup>/<start-number>-<end-number> */
339 	*list++ = '\0';
340     }
341     if (fmInitialized) {
342 	message(Sprintf("Reading newsgroup %s...", group)->ptr, 0, 0);
343 	refresh();
344     }
345     qgroup = html_quote(group);
346     group = file_unquote(group);
347     page = Strnew_m_charp("<html>\n<head>\n<base href=\"",
348 			  parsedURL2Str(pu)->ptr, "\">\n<title>Newsgroup: ",
349 			  qgroup, "</title>\n</head>\n<body>\n<h1>Newsgroup: ",
350 			  qgroup, "</h1>\n<hr>\n", NULL);
351 
352     if (SETJMP(AbortLoading) != 0) {
353 	news_close(&current_news);
354 	Strcat_charp(page, "</table>\n<p>Transfer Interrupted!\n");
355 	goto news_end;
356     }
357     TRAP_ON;
358 
359     tmp = news_command(&current_news, "GROUP", group, &status);
360     if (status != 211)
361 	goto news_list;
362     if (sscanf(tmp->ptr, "%d %d %d %d", &status, &i, &first, &last) != 4)
363 	goto news_list;
364     if (list && *list) {
365 	if ((p = strchr(list, '-'))) {
366 	    *p++ = '\0';
367 	    end = atoi(p);
368 	}
369 	start = atoi(list);
370 	if (start > 0) {
371 	    if (start < first)
372 		start = first;
373 	    if (end <= 0)
374 		end = start + MaxNewsMessage - 1;
375 	}
376     }
377     if (start <= 0) {
378 	start = first;
379 	end = last;
380 	if (end - start > MaxNewsMessage - 1)
381 	    start = end - MaxNewsMessage + 1;
382     }
383     page = Sprintf("<html>\n<head>\n<base href=\"%s\">\n\
384 <title>Newsgroup: %s %d-%d</title>\n\
385 </head>\n<body>\n<h1>Newsgroup: %s %d-%d</h1>\n<hr>\n", parsedURL2Str(pu)->ptr, qgroup, start, end, qgroup, start, end);
386     if (start > first) {
387 	i = start - MaxNewsMessage;
388 	if (i < first)
389 	    i = first;
390 	Strcat(page, Sprintf("<a href=\"%s%s/%d-%d\">[%d-%d]</a>\n", scheme,
391 			     qgroup, i, start - 1, i, start - 1));
392     }
393 
394     Strcat_charp(page, "<table>\n");
395     news_command(&current_news, "XOVER", Sprintf("%d-%d", start, end)->ptr,
396 		 &status);
397     if (status == 224) {
398 	f.scheme = SCM_NEWS;
399 	while (1) {
400 	    tmp = StrISgets(current_news.rf);
401 	    if (NEWS_ENDLINE(tmp->ptr))
402 		break;
403 	    if (sscanf(tmp->ptr, "%d", &i) != 1)
404 		continue;
405 	    if (!(s = strchr(tmp->ptr, '\t')))
406 		continue;
407 	    s++;
408 	    if (!(n = strchr(s, '\t')))
409 		continue;
410 	    *n++ = '\0';
411 	    if (!(t = strchr(n, '\t')))
412 		continue;
413 	    *t++ = '\0';
414 	    if (!(p = strchr(t, '\t')))
415 		continue;
416 	    *p++ = '\0';
417 	    if (*p == '<')
418 		p++;
419 	    if (!(q = strchr(p, '>')) && !(q = strchr(p, '\t')))
420 		continue;
421 	    *q = '\0';
422 	    tmp = decodeMIME(Strnew_charp(s), &mime_charset);
423 	    s = convertLine(&f, tmp, HEADER_MODE,
424 			    mime_charset ? &mime_charset : charset,
425 			    mime_charset ? mime_charset : doc_charset)->ptr;
426 	    tmp = decodeMIME(Strnew_charp(n), &mime_charset);
427 	    n = convertLine(&f, tmp, HEADER_MODE,
428 			    mime_charset ? &mime_charset : charset,
429 			    mime_charset ? mime_charset : doc_charset)->ptr;
430 	    add_news_message(page, i, t, n, s, p, scheme,
431 			     pu->scheme == SCM_NNTP_GROUP ? qgroup : NULL);
432 	}
433     }
434     else {
435 	init_stream(&f, SCM_NEWS, current_news.rf);
436 	buf = newBuffer(INIT_BUFFER_WIDTH);
437 	for (i = start; i <= end && i <= last; i++) {
438 	    news_command(&current_news, "HEAD", Sprintf("%d", i)->ptr,
439 			 &status);
440 	    if (status != 221)
441 		continue;
442 	    readHeader(&f, buf, FALSE, NULL);
443 	    if (!(p = checkHeader(buf, "Message-ID:")))
444 		continue;
445 	    if (*p == '<')
446 		p++;
447 	    if (!(q = strchr(p, '>')) && !(q = strchr(p, '\t')))
448 		*q = '\0';
449 	    if (!(s = checkHeader(buf, "Subject:")))
450 		continue;
451 	    if (!(n = checkHeader(buf, "From:")))
452 		continue;
453 	    if (!(t = checkHeader(buf, "Date:")))
454 		continue;
455 	    add_news_message(page, i, t, n, s, p, scheme,
456 			     pu->scheme == SCM_NNTP_GROUP ? qgroup : NULL);
457 	}
458     }
459     Strcat_charp(page, "</table>\n");
460 
461     if (end < last) {
462 	i = end + MaxNewsMessage;
463 	if (i > last)
464 	    i = last;
465 	Strcat(page, Sprintf("<a href=\"%s%s/%d-%d\">[%d-%d]</a>\n", scheme,
466 			     qgroup, end + 1, i, end + 1, i));
467     }
468     flag = 1;
469 
470   news_list:
471     tmp = Sprintf("ACTIVE %s", group);
472     if (!strchr(group, '*'))
473 	Strcat_charp(tmp, ".*");
474     news_command(&current_news, "LIST", tmp->ptr, &status);
475     if (status != 215)
476 	goto news_end;
477     while (1) {
478 	tmp = StrISgets(current_news.rf);
479 	if (NEWS_ENDLINE(tmp->ptr))
480 	    break;
481 	if (flag < 2) {
482 	    if (flag == 1)
483 		Strcat_charp(page, "<hr>\n");
484 	    Strcat_charp(page, "<table>\n");
485 	    flag = 2;
486 	}
487 	p = tmp->ptr;
488 	for (q = p; *q && !IS_SPACE(*q); q++) ;
489 	*(q++) = '\0';
490 	if (sscanf(q, "%d %d", &last, &first) == 2 && last >= first)
491 	    i = last - first + 1;
492 	else
493 	    i = 0;
494 	Strcat(page,
495 	       Sprintf
496 	       ("<tr><td align=right>%d<td><a href=\"%s%s\">%s</a>\n", i,
497 		scheme, html_quote(file_quote(p)), html_quote(p)));
498     }
499     if (flag == 2)
500 	Strcat_charp(page, "</table>\n");
501 
502   news_end:
503     Strcat_charp(page, "</body>\n</html>\n");
504     TRAP_OFF;
505     return page;
506 }
507 
508 void
closeNews(void)509 closeNews(void)
510 {
511     news_close(&current_news);
512 }
513 
514 void
disconnectNews(void)515 disconnectNews(void)
516 {
517     news_quit(&current_news);
518 }
519 
520 #endif				/* USE_NNTP */
521