news.c (12222B)
1 /* $Id$ */ 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 = " "; 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(¤t_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(¤t_news, "MODE", mode ? mode : "READER", 264 &status); 265 if (status != 200 && status != 201) 266 news_close(¤t_news); 267 } 268 else 269 news_quit(¤t_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(¤t_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(¤t_news, "GROUP", group, &status); 290 if (status != 211) 291 return NULL; 292 } 293 if (strchr(p, '@')) /* <message-id> */ 294 news_command(¤t_news, "ARTICLE", Sprintf("<%s>", p)->ptr, 295 &status); 296 else /* <article-number> */ 297 news_command(¤t_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(¤t_news); 352 Strcat_charp(page, "</table>\n<p>Transfer Interrupted!\n"); 353 goto news_end; 354 } 355 TRAP_ON; 356 357 tmp = news_command(¤t_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(¤t_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(¤t_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(¤t_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(¤t_news); 510 } 511 512 void 513 disconnectNews(void) 514 { 515 news_quit(¤t_news); 516 } 517 518 #endif /* USE_NNTP */