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