w3m

Unnamed repository; edit this file to name it for gitweb.
git clone https://logand.com/git/w3m.git/
Log | Files | Refs | README

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 = "&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 */