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 = " ";
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(¤t_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(¤t_news, "MODE", mode ? mode : "READER",
266 &status);
267 if (status != 200 && status != 201)
268 news_close(¤t_news);
269 }
270 else
271 news_quit(¤t_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(¤t_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(¤t_news, "GROUP", group, &status);
292 if (status != 211)
293 return NULL;
294 }
295 if (strchr(p, '@')) /* <message-id> */
296 news_command(¤t_news, "ARTICLE", Sprintf("<%s>", p)->ptr,
297 &status);
298 else /* <article-number> */
299 news_command(¤t_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(¤t_news);
354 Strcat_charp(page, "</table>\n<p>Transfer Interrupted!\n");
355 goto news_end;
356 }
357 TRAP_ON;
358
359 tmp = news_command(¤t_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(¤t_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(¤t_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(¤t_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(¤t_news);
512 }
513
514 void
disconnectNews(void)515 disconnectNews(void)
516 {
517 news_quit(¤t_news);
518 }
519
520 #endif /* USE_NNTP */
521