/*
* $LynxId: HTNews.c,v 1.74 2019/08/20 09:03:18 tom Exp $
*
* NEWS ACCESS HTNews.c
* ===========
*
* History:
* 26 Sep 90 Written TBL
* 29 Nov 91 Downgraded to C, for portable implementation.
*/
#include <HTUtils.h> /* Coding convention macros */
#ifndef DISABLE_NEWS
/* Implements:
*/
#include <HTNews.h>
#include <HTCJK.h>
#include <HTMIME.h>
#include <HTFont.h>
#include <HTFormat.h>
#include <HTTCP.h>
#include <LYUtils.h>
#include <LYStrings.h>
#define NEWS_PORT 119 /* See rfc977 */
#define SNEWS_PORT 563 /* See Lou Montulli */
#define APPEND /* Use append methods */
int HTNewsChunkSize = 30; /* Number of articles for quick display */
int HTNewsMaxChunk = 40; /* Largest number of articles in one window */
#ifndef DEFAULT_NEWS_HOST
#define DEFAULT_NEWS_HOST "news"
#endif /* DEFAULT_NEWS_HOST */
#ifndef NEWS_SERVER_FILE
#define NEWS_SERVER_FILE "/usr/local/lib/rn/server"
#endif /* NEWS_SERVER_FILE */
#ifndef NEWS_AUTH_FILE
#define NEWS_AUTH_FILE ".newsauth"
#endif /* NEWS_AUTH_FILE */
#ifdef USE_SSL
#if defined(LIBRESSL_VERSION_NUMBER)
/* OpenSSL and LibreSSL version numbers do not correspond */
#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
#undef SSL_load_error_strings
#define SSL_load_error_strings() /* nothing */
#endif
static SSL *Handle = NULL;
static int channel_s = 1;
#define NEWS_NETWRITE(sock, buff, size) \
((Handle != NULL) \
? SSL_write(Handle, buff, size) \
: NETWRITE(sock, buff, size))
#define NEWS_NETCLOSE(sock) \
{ \
if ((int)(sock) >= 0) { \
(void)NETCLOSE(sock); \
} \
if (Handle != NULL) { \
SSL_free(Handle); \
Handle = NULL; \
} \
}
static int HTNewsGetCharacter(void);
#define NEXT_CHAR HTNewsGetCharacter()
#else
#define NEWS_NETWRITE NETWRITE
#define NEWS_NETCLOSE NETCLOSE
#define NEXT_CHAR HTGetCharacter()
#endif /* USE_SSL */
#include <HTML.h>
#include <HTAccess.h>
#include <HTParse.h>
#include <HTFormat.h>
#include <HTAlert.h>
#include <LYNews.h>
#include <LYGlobalDefs.h>
#include <LYLeaks.h>
#define SnipIn(d,fmt,len,s) sprintf(d, fmt, (int)sizeof(d)-len, s)
#define SnipIn2(d,fmt,tag,len,s) sprintf(d, fmt, tag, (int)sizeof(d)-len, s)
struct _HTStructured {
const HTStructuredClass *isa;
/* ... */
};
#define LINE_LENGTH 512 /* Maximum length of line of ARTICLE etc */
#define GROUP_NAME_LENGTH 256 /* Maximum length of group name */
/*
* Module-wide variables.
*/
char *HTNewsHost = NULL; /* Default host */
static char *NewsHost = NULL; /* Current host */
static char *NewsHREF = NULL; /* Current HREF prefix */
static int s; /* Socket for NewsHost */
static int HTCanPost = FALSE; /* Current POST permission */
static char response_text[LINE_LENGTH + 1]; /* Last response */
static HTStructured *target; /* The output sink */
static HTStructuredClass targetClass; /* Copy of fn addresses */
static HTStream *rawtarget = NULL; /* The output sink for rawtext */
static HTStreamClass rawtargetClass; /* Copy of fn addresses */
static int diagnostic; /* level: 0=none 2=source */
static BOOL rawtext = NO; /* Flag: HEAD or -mime_headers */
static HTList *NNTP_AuthInfo = NULL; /* AUTHINFO database */
static char *name = NULL;
static char *address = NULL;
static char *dbuf = NULL; /* dynamic buffer for long messages etc. */
#define PUTC(c) (*targetClass.put_character)(target, c)
#define PUTS(s) (*targetClass.put_string)(target, s)
#define RAW_PUTS(s) (*rawtargetClass.put_string)(rawtarget, s)
#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
#define END(e) (*targetClass.end_element)(target, e, 0)
#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
(*targetClass.end_element)(target, e, 0)
#define FREE_TARGET if (rawtext) (*rawtargetClass._free)(rawtarget); \
else (*targetClass._free)(target)
#define ABORT_TARGET if (rawtext) (*rawtargetClass._abort)(rawtarget, NULL); \
else (*targetClass._abort)(target, NULL)
typedef struct _NNTPAuth {
char *host;
char *user;
char *pass;
} NNTPAuth;
#ifdef LY_FIND_LEAKS
static void free_news_globals(void)
{
if (s >= 0) {
NEWS_NETCLOSE(s);
s = -1;
}
FREE(HTNewsHost);
FREE(NewsHost);
FREE(NewsHREF);
FREE(name);
FREE(address);
FREE(dbuf);
}
#endif /* LY_FIND_LEAKS */
static void free_NNTP_AuthInfo(void)
{
HTList *cur = NNTP_AuthInfo;
NNTPAuth *auth = NULL;
if (!cur)
return;
while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
FREE(auth->host);
FREE(auth->user);
FREE(auth->pass);
FREE(auth);
}
HTList_delete(NNTP_AuthInfo);
NNTP_AuthInfo = NULL;
return;
}
/*
* Initialize the authentication list by loading the user's $HOME/.newsauth
* file. That file is part of tin's configuration and is used by a few other
* programs.
*/
static void load_NNTP_AuthInfo(void)
{
FILE *fp;
char fname[LY_MAXPATH];
char buffer[LINE_LENGTH + 1];
LYAddPathToHome(fname, sizeof(fname), NEWS_AUTH_FILE);
if ((fp = fopen(fname, "r")) != 0) {
while (fgets(buffer, (int) sizeof(buffer), fp) != 0) {
char the_host[LINE_LENGTH + 1];
char the_pass[LINE_LENGTH + 1];
char the_user[LINE_LENGTH + 1];
if (sscanf(buffer, "%s%s%s", the_host, the_pass, the_user) == 3
&& strlen(the_host) != 0
&& strlen(the_pass) != 0
&& strlen(the_user) != 0) {
NNTPAuth *auth = typecalloc(NNTPAuth);
if (auth == NULL)
break;
StrAllocCopy(auth->host, the_host);
StrAllocCopy(auth->pass, the_pass);
StrAllocCopy(auth->user, the_user);
HTList_appendObject(NNTP_AuthInfo, auth);
}
}
fclose(fp);
}
}
const char *HTGetNewsHost(void)
{
return HTNewsHost;
}
void HTSetNewsHost(const char *value)
{
StrAllocCopy(HTNewsHost, value);
}
/* Initialisation for this module
* ------------------------------
*
* Except on the NeXT, we pick up the NewsHost name from
*
* 1. Environment variable NNTPSERVER
* 2. File NEWS_SERVER_FILE
* 3. Compilation time macro DEFAULT_NEWS_HOST
* 4. Default to "news"
*
* On the NeXT, we pick up the NewsHost name from, in order:
*
* 1. WorldWideWeb default "NewsHost"
* 2. Global default "NewsHost"
* 3. News default "NewsHost"
* 4. Compilation time macro DEFAULT_NEWS_HOST
* 5. Default to "news"
*/
static BOOL initialized = NO;
static BOOL initialize(void)
{
#ifdef NeXTStep
char *cp = NULL;
#endif
/*
* Get name of Host.
*/
#ifdef NeXTStep
if ((cp = NXGetDefaultValue("WorldWideWeb", "NewsHost")) == 0) {
if ((cp = NXGetDefaultValue("News", "NewsHost")) == 0) {
StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
}
}
if (cp) {
StrAllocCopy(HTNewsHost, cp);
cp = NULL;
}
#else
if (LYGetEnv("NNTPSERVER")) {
StrAllocCopy(HTNewsHost, LYGetEnv("NNTPSERVER"));
CTRACE((tfp, "HTNews: NNTPSERVER defined as `%s'\n",
HTNewsHost));
} else {
FILE *fp = fopen(NEWS_SERVER_FILE, TXT_R);
if (fp) {
char server_name[MAXHOSTNAMELEN + 1];
if (fgets(server_name, (int) sizeof server_name, fp) != NULL) {
char *p = StrChr(server_name, '\n');
if (p != NULL)
*p = '\0';
StrAllocCopy(HTNewsHost, server_name);
CTRACE((tfp, "HTNews: File %s defines news host as `%s'\n",
NEWS_SERVER_FILE, HTNewsHost));
}
fclose(fp);
}
}
if (!HTNewsHost)
StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
#endif /* NeXTStep */
s = -1; /* Disconnected */
#ifdef LY_FIND_LEAKS
atexit(free_news_globals);
#endif
return YES;
}
/* Send NNTP Command line to remote host & Check Response
* ------------------------------------------------------
*
* On entry,
* command points to the command to be sent, including CRLF, or is null
* pointer if no command to be sent.
* On exit,
* Negative status indicates transmission error, socket closed.
* Positive status is an NNTP status.
*/
static int response(char *command)
{
int result;
char *p = response_text;
int ich;
if (command) {
int status;
int length = (int) strlen(command);
CTRACE((tfp, "NNTP command to be sent: %s", command));
#ifdef NOT_ASCII
{
const char *p2;
char *q;
char ascii[LINE_LENGTH + 1];
for (p2 = command, q = ascii; *p2; p2++, q++) {
*q = TOASCII(*p2);
}
status = NEWS_NETWRITE(s, ascii, length);
}
#else
status = (int) NEWS_NETWRITE(s, (char *) command, length);
#endif /* NOT_ASCII */
if (status < 0) {
CTRACE((tfp, "HTNews: Unable to send command. Disconnecting.\n"));
NEWS_NETCLOSE(s);
s = -1;
return status;
} /* if bad status */
}
/* if command to be sent */
for (;;) {
ich = NEXT_CHAR;
if (((*p++ = (char) ich) == LF) ||
(p == &response_text[LINE_LENGTH])) {
*--p = '\0'; /* Terminate the string */
CTRACE((tfp, "NNTP Response: %s\n", response_text));
sscanf(response_text, "%d", &result);
return result;
}
/* if end of line */
if (ich == EOF) {
*(p - 1) = '\0';
if (interrupted_in_htgetcharacter) {
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
} else {
CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n",
s));
}
NEWS_NETCLOSE(s); /* End of file, close socket */
s = -1;
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
return (HT_INTERRUPTED);
}
return ((int) EOF); /* End of file on response */
}
} /* Loop over characters */
}
/* Case insensitive string comparisons
* -----------------------------------
*
* On entry,
* template must be already in upper case.
* unknown may be in upper or lower or mixed case to match.
*/
static BOOL match(const char *unknown, const char *ctemplate)
{
const char *u = unknown;
const char *t = ctemplate;
for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) ; /* Find mismatch or end */
return (BOOL) (*t == 0); /* OK if end of template */
}
typedef enum {
NNTPAUTH_ERROR = 0, /* general failure */
NNTPAUTH_OK = 281, /* authenticated successfully */
NNTPAUTH_CLOSE = 502 /* server probably closed connection */
} NNTPAuthResult;
/*
* This function handles nntp authentication. - FM
*/
static NNTPAuthResult HTHandleAuthInfo(char *host)
{
HTList *cur = NULL;
NNTPAuth *auth = NULL;
char *UserName = NULL;
char *PassWord = NULL;
char *msg = NULL;
char buffer[512];
int status, tries;
/*
* Make sure we have a host. - FM
*/
if (isEmpty(host))
return NNTPAUTH_ERROR;
/*
* Check for an existing authorization entry. - FM
*/
if (NNTP_AuthInfo == NULL) {
NNTP_AuthInfo = HTList_new();
load_NNTP_AuthInfo();
#ifdef LY_FIND_LEAKS
atexit(free_NNTP_AuthInfo);
#endif
}
cur = NNTP_AuthInfo;
while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
if (!strcmp(auth->host, host)) {
UserName = auth->user;
PassWord = auth->pass;
break;
}
}
/*
* Handle the username. - FM
*/
buffer[sizeof(buffer) - 1] = '\0';
tries = 3;
while (tries) {
if (UserName == NULL) {
HTSprintf0(&msg, gettext("Username for news host '%s':"), host);
UserName = HTPrompt(msg, NULL);
FREE(msg);
if (!(UserName && *UserName)) {
FREE(UserName);
return NNTPAUTH_ERROR;
}
}
sprintf(buffer, "AUTHINFO USER %.*s%c%c",
(int) sizeof(buffer) - 17, UserName, CR, LF);
if ((status = response(buffer)) < 0) {
if (status == HT_INTERRUPTED)
_HTProgress(CONNECTION_INTERRUPTED);
else
HTAlert(FAILED_CONNECTION_CLOSED);
if (auth) {
if (auth->user != UserName) {
FREE(auth->user);
auth->user = UserName;
}
} else {
FREE(UserName);
}
return NNTPAUTH_CLOSE;
}
if (status == 281) {
/*
* Username is accepted and no password is required. - FM
*/
if (auth) {
if (auth->user != UserName) {
FREE(auth->user);
auth->user = UserName;
}
} else {
/*
* Store the accepted username and no password. - FM
*/
if ((auth = typecalloc(NNTPAuth)) != NULL) {
StrAllocCopy(auth->host, host);
auth->user = UserName;
HTList_appendObject(NNTP_AuthInfo, auth);
}
}
return NNTPAUTH_OK;
}
if (status != 381) {
/*
* Not success, nor a request for the password, so it must be an
* error. - FM
*/
HTAlert(response_text);
tries--;
if ((tries > 0) && HTConfirm(gettext("Change username?"))) {
if (!auth || auth->user != UserName) {
FREE(UserName);
}
if ((UserName = HTPrompt(gettext("Username:"), UserName))
!= NULL &&
*UserName) {
continue;
}
}
if (auth) {
if (auth->user != UserName) {
FREE(auth->user);
}
FREE(auth->pass);
}
FREE(UserName);
return NNTPAUTH_ERROR;
}
break;
}
if (status == 381) {
/*
* Handle the password. - FM
*/
tries = 3;
while (tries) {
if (PassWord == NULL) {
HTSprintf0(&msg, gettext("Password for news host '%s':"), host);
PassWord = HTPromptPassword(msg, NULL);
FREE(msg);
if (!(PassWord && *PassWord)) {
FREE(PassWord);
return NNTPAUTH_ERROR;
}
}
sprintf(buffer, "AUTHINFO PASS %.*s%c%c",
(int) sizeof(buffer) - 17, PassWord, CR, LF);
if ((status = response(buffer)) < 0) {
if (status == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
} else {
HTAlert(FAILED_CONNECTION_CLOSED);
}
if (auth) {
if (auth->user != UserName) {
FREE(auth->user);
auth->user = UserName;
}
if (auth->pass != PassWord) {
FREE(auth->pass);
auth->pass = PassWord;
}
} else {
FREE(UserName);
FREE(PassWord);
}
return NNTPAUTH_CLOSE;
}
if (status == 502) {
/*
* That's what INN's nnrpd returns. It closes the connection
* after this. - kw
*/
HTAlert(response_text);
if (auth) {
if (auth->user == UserName)
UserName = NULL;
FREE(auth->user);
if (auth->pass == PassWord)
PassWord = NULL;
FREE(auth->pass);
}
FREE(UserName);
FREE(PassWord);
return NNTPAUTH_CLOSE;
}
if (status == 281) {
/*
* Password also is accepted, and everything has been stored.
* - FM
*/
if (auth) {
if (auth->user != UserName) {
FREE(auth->user);
auth->user = UserName;
}
if (auth->pass != PassWord) {
FREE(auth->pass);
auth->pass = PassWord;
}
} else {
if ((auth = typecalloc(NNTPAuth)) != NULL) {
StrAllocCopy(auth->host, host);
auth->user = UserName;
auth->pass = PassWord;
HTList_appendObject(NNTP_AuthInfo, auth);
}
}
return NNTPAUTH_OK;
}
/*
* Not success, so it must be an error. - FM
*/
HTAlert(response_text);
if (!auth || auth->pass != PassWord) {
FREE(PassWord);
} else {
PassWord = NULL;
}
tries--;
if ((tries > 0) && HTConfirm(gettext("Change password?"))) {
continue;
}
if (auth) {
if (auth->user == UserName)
UserName = NULL;
FREE(auth->user);
FREE(auth->pass);
}
FREE(UserName);
break;
}
}
return NNTPAUTH_ERROR;
}
/* Find Author's name in mail address
* ----------------------------------
*
* On exit,
* Returns allocated string which cannot be freed by the
* calling function, and is reallocated on subsequent calls
* to this function.
*
* For example, returns "Tim Berners-Lee" if given any of
* " Tim Berners-Lee <tim@online.cern.ch> "
* or " tim@online.cern.ch ( Tim Berners-Lee ) "
*/
static char *author_name(char *email)
{
char *p, *e;
StrAllocCopy(name, email);
CTRACE((tfp, "Trying to find name in: %s\n", name));
if ((p = strrchr(name, '(')) && (e = strrchr(name, ')'))) {
if (e > p) {
*e = '\0'; /* Chop off everything after the ')' */
return HTStrip(p + 1); /* Remove leading and trailing spaces */
}
}
if ((p = strrchr(name, '<')) && (e = strrchr(name, '>'))) {
if (e++ > p) {
while ((*p++ = *e++) != 0) /* Remove <...> */
;
return HTStrip(name); /* Remove leading and trailing spaces */
}
}
return HTStrip(name); /* Default to the whole thing */
}
/* Find Author's mail address
* --------------------------
*
* On exit,
* Returns allocated string which cannot be freed by the
* calling function, and is reallocated on subsequent calls
* to this function.
*
* For example, returns "montulli@spaced.out.galaxy.net" if given any of
* " Lou Montulli <montulli@spaced.out.galaxy.net> "
* or " montulli@spaced.out.galaxy.net ( Lou "The Stud" Montulli ) "
*/
static char *author_address(char *email)
{
char *p, *at, *e;
StrAllocCopy(address, email);
CTRACE((tfp, "Trying to find address in: %s\n", address));
if ((p = strrchr(address, '<'))) {
if ((e = strrchr(p, '>')) && (at = strrchr(p, '@'))) {
if (at < e) {
*e = '\0'; /* Remove > */
return HTStrip(p + 1); /* Remove leading and trailing spaces */
}
}
}
if ((p = strrchr(address, '(')) &&
(e = strrchr(address, ')')) && (at = StrChr(address, '@'))) {
if (e > p && at < e) {
*p = '\0'; /* Chop off everything after the ')' */
return HTStrip(address); /* Remove leading and trailing spaces */
}
}
if ((at = strrchr(address, '@')) && at > address) {
p = (at - 1);
e = (at + 1);
while (p > address && !isspace(UCH(*p)))
p--;
while (*e && !isspace(UCH(*e)))
e++;
*e = 0;
return HTStrip(p);
}
/*
* Default to the first word.
*/
p = address;
while (isspace(UCH(*p)))
p++; /* find first non-space */
e = p;
while (!isspace(UCH(*e)) && *e != '\0')
e++; /* find next space or end */
*e = '\0'; /* terminate space */
return (p);
}
/* Start anchor element
* --------------------
*/
static void start_anchor(const char *href)
{
BOOL present[HTML_A_ATTRIBUTES];
const char *value[HTML_A_ATTRIBUTES];
int i;
for (i = 0; i < HTML_A_ATTRIBUTES; i++)
present[i] = (BOOL) (i == HTML_A_HREF);
value[HTML_A_HREF] = href;
(*targetClass.start_element) (target, HTML_A, present, value, -1, 0);
}
/* Start link element
* ------------------
*/
static void start_link(const char *href, const char *rev)
{
BOOL present[HTML_LINK_ATTRIBUTES];
const char *value[HTML_LINK_ATTRIBUTES];
int i;
for (i = 0; i < HTML_LINK_ATTRIBUTES; i++)
present[i] = (BOOL) (i == HTML_LINK_HREF || i == HTML_LINK_REV);
value[HTML_LINK_HREF] = href;
value[HTML_LINK_REV] = rev;
(*targetClass.start_element) (target, HTML_LINK, present, value, -1, 0);
}
/* Start list element
* ------------------
*/
static void start_list(int seqnum)
{
BOOL present[HTML_OL_ATTRIBUTES];
const char *value[HTML_OL_ATTRIBUTES];
char SeqNum[20];
int i;
for (i = 0; i < HTML_OL_ATTRIBUTES; i++)
present[i] = (BOOL) (i == HTML_OL_SEQNUM || i == HTML_OL_START);
sprintf(SeqNum, "%d", seqnum);
value[HTML_OL_SEQNUM] = SeqNum;
value[HTML_OL_START] = SeqNum;
(*targetClass.start_element) (target, HTML_OL, present, value, -1, 0);
}
/* Paste in an Anchor
* ------------------
*
*
* On entry,
* HT has a selection of zero length at the end.
* text points to the text to be put into the file, 0 terminated.
* addr points to the hypertext reference address,
* terminated by white space, comma, NULL or '>'
*/
static void write_anchor(const char *text, const char *addr)
{
char href[LINE_LENGTH + 1];
const char *p;
char *q;
for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++) {
;
}
if (strlen(NewsHREF) + (size_t) (p - addr) + 1 < sizeof(href)) {
q = href;
strcpy(q, NewsHREF);
/* Make complete hypertext reference */
StrNCat(q, addr, (size_t) (p - addr));
} else {
q = NULL;
HTSprintf0(&q, "%s%.*s", NewsHREF, (int) (p - addr), addr);
}
start_anchor(q);
PUTS(text);
END(HTML_A);
if (q != href)
FREE(q);
}
/* Write list of anchors
* ---------------------
*
* We take a pointer to a list of objects, and write out each,
* generating an anchor for each.
*
* On entry,
* HT has a selection of zero length at the end.
* text points to a comma or space separated list of addresses.
* On exit,
* *text is NOT any more chopped up into substrings.
*/
static void write_anchors(char *text)
{
char *start = text;
char *end;
char c;
for (;;) {
for (; *start && (WHITE(*start)); start++) ; /* Find start */
if (!*start)
return; /* (Done) */
for (end = start;
*end && (*end != ' ') && (*end != ','); end++) ; /* Find end */
if (*end)
end++; /* Include comma or space but not NULL */
c = *end;
*end = '\0';
if (*start == '<')
write_anchor(start, start + 1);
else
write_anchor(start, start);
START(HTML_BR);
*end = c;
start = end; /* Point to next one */
}
}
/* Abort the connection abort_socket
* --------------------
*/
static void abort_socket(void)
{
CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n", s));
NEWS_NETCLOSE(s); /* End of file, close socket */
if (rawtext) {
RAW_PUTS("Network Error: connection lost\n");
} else {
PUTS("Network Error: connection lost");
PUTC('\n');
}
s = -1; /* End of file on response */
}
/*
* Determine if a line is a valid header line. valid_header
* -------------------------------------------
*/
static BOOLEAN valid_header(char *line)
{
char *colon, *space;
/*
* Blank or tab in first position implies this is a continuation header.
*/
if (line[0] == ' ' || line[0] == '\t')
return (TRUE);
/*
* Just check for initial letter, colon, and space to make sure we discard
* only invalid headers.
*/
colon = StrChr(line, ':');
space = StrChr(line, ' ');
if (isalpha(UCH(line[0])) && colon && space == colon + 1)
return (TRUE);
/*
* Anything else is a bad header -- it should be ignored.
*/
return (FALSE);
}
/* post in an Article post_article
* ------------------
* (added by FM, modeled on Lynx's previous mini inews)
*
* Note the termination condition of a single dot on a line by itself.
*
* On entry,
* s Global socket number is OK
* postfile file with header and article to post.
*/
static void post_article(char *postfile)
{
char line[512];
char buf[512];
char crlf[3];
char *cp;
int status;
FILE *fd;
int in_header = 1, seen_header = 0, seen_fromline = 0;
int blen = 0, llen = 0;
/*
* Open the temporary file with the nntp headers and message body. - FM
*/
if ((fd = fopen(NonNull(postfile), TXT_R)) == NULL) {
HTAlert(FAILED_CANNOT_OPEN_POST);
return;
}
/*
* Read the temporary file and post in maximum 512 byte chunks. - FM
*/
buf[0] = '\0';
sprintf(crlf, "%c%c", CR, LF);
while (fgets(line, (int) sizeof(line) - 2, fd) != NULL) {
if ((cp = StrChr(line, '\n')) != NULL)
*cp = '\0';
if (line[0] == '.') {
/*
* A single '.' means end of transmission for nntp. Lead dots on
* lines normally are trimmed and the EOF is not registered if the
* dot was not followed by CRLF. We prepend an extra dot for any
* line beginning with one, to retain the one intended, as well as
* avoid a false EOF signal. We know we have room for it in the
* buffer, because we normally send when it would exceed 510. - FM
*/
strcat(buf, ".");
blen++;
}
llen = (int) strlen(line);
if (in_header && !strncasecomp(line, "From:", 5)) {
seen_header = 1;
seen_fromline = 1;
}
if (in_header && line[0] == '\0') {
if (seen_header) {
in_header = 0;
if (!seen_fromline) {
if (blen >= (int) sizeof(buf) - 35) {
IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
buf[blen = 0] = 0;
}
strcat(buf, "From: anonymous@nowhere.you.know");
strcat(buf, crlf);
blen += 34;
}
} else {
continue;
}
} else if (in_header) {
if (valid_header(line)) {
seen_header = 1;
} else {
continue;
}
}
strcat(line, crlf);
llen += 2;
if ((blen + llen) >= (int) sizeof(buf) - 1) {
IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
buf[blen = 0] = 0;
}
strcat(buf, line);
blen += llen;
}
fclose(fd);
HTSYS_remove(postfile);
/*
* Send the nntp EOF and get the server's response. - FM
*/
if (blen >= (int) sizeof(buf) - 4) {
IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
buf[blen = 0] = 0;
}
strcat(buf, ".");
strcat(buf, crlf);
blen += 3;
IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
status = response(NULL);
if (status == 240) {
/*
* Successful post. - FM
*/
HTProgress(response_text);
} else {
/*
* Shucks, something went wrong. - FM
*/
HTAlert(response_text);
}
}
#ifdef NEWS_DEBUG
/* for DEBUG 1997/11/07 (Fri) 17:20:16 */
void debug_print(unsigned char *p)
{
while (*p) {
if (*p == '\0')
break;
if (*p == 0x1b)
printf("[ESC]");
else if (*p == '\n')
printf("[NL]");
else if (*p < ' ' || *p >= 0x80)
printf("(%02x)", *p);
else
putchar(*p);
p++;
}
printf("]\n");
}
#endif
static char *decode_mime(char **str)
{
static char empty[] = "";
#ifdef SH_EX
if (HTCJK != JAPANESE)
return *str;
#endif
HTmmdecode(str, *str);
return HTrjis(str, *str) ? *str : empty;
}
/* Read in an Article read_article
* ------------------
*
* Note the termination condition of a single dot on a line by itself.
* RFC 977 specifies that the line "folding" of RFC850 is not used, so we
* do not handle it here.
*
* On entry,
* s Global socket number is OK
* HT Global hypertext object is ready for appending text
*/
static int read_article(HTParentAnchor *thisanchor)
{
char line[LINE_LENGTH + 1];
char *full_line = NULL;
char *subject = NULL; /* Subject string */
char *from = NULL; /* From string */
char *replyto = NULL; /* Reply-to string */
char *date = NULL; /* Date string */
char *organization = NULL; /* Organization string */
char *references = NULL; /* Hrefs for other articles */
char *newsgroups = NULL; /* Newsgroups list */
char *followupto = NULL; /* Followup list */
char *href = NULL;
char *p = line;
char *cp;
const char *ccp;
BOOL done = NO;
/*
* Read in the HEADer of the article.
*
* The header fields are either ignored, or formatted and put into the
* text.
*/
if (!diagnostic && !rawtext) {
while (!done) {
int ich = NEXT_CHAR;
*p++ = (char) ich;
if (ich == EOF) {
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
}
abort_socket(); /* End of file, close socket */
return (HT_LOADED); /* End of file on response */
}
if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
*--p = '\0'; /* Terminate the string */
CTRACE((tfp, "H %s\n", line));
if (line[0] == '\t' || line[0] == ' ') {
int i = 0;
while (line[i]) {
if (line[i] == '\t')
line[i] = ' ';
i++;
}
if (full_line == NULL) {
StrAllocCopy(full_line, line);
} else {
StrAllocCat(full_line, line);
}
} else {
StrAllocCopy(full_line, line);
}
if (full_line[0] == '.') {
/*
* End of article?
*/
if (UCH(full_line[1]) < ' ') {
done = YES;
break;
}
} else if (UCH(full_line[0]) < ' ') {
break; /* End of Header? */
} else if (match(full_line, "SUBJECT:")) {
StrAllocCopy(subject, HTStrip(StrChr(full_line, ':') + 1));
decode_mime(&subject);
} else if (match(full_line, "DATE:")) {
StrAllocCopy(date, HTStrip(StrChr(full_line, ':') + 1));
} else if (match(full_line, "ORGANIZATION:")) {
StrAllocCopy(organization,
HTStrip(StrChr(full_line, ':') + 1));
decode_mime(&organization);
} else if (match(full_line, "FROM:")) {
StrAllocCopy(from, HTStrip(StrChr(full_line, ':') + 1));
decode_mime(&from);
} else if (match(full_line, "REPLY-TO:")) {
StrAllocCopy(replyto, HTStrip(StrChr(full_line, ':') + 1));
decode_mime(&replyto);
} else if (match(full_line, "NEWSGROUPS:")) {
StrAllocCopy(newsgroups, HTStrip(StrChr(full_line, ':') + 1));
} else if (match(full_line, "REFERENCES:")) {
StrAllocCopy(references, HTStrip(StrChr(full_line, ':') + 1));
} else if (match(full_line, "FOLLOWUP-TO:")) {
StrAllocCopy(followupto, HTStrip(StrChr(full_line, ':') + 1));
} else if (match(full_line, "MESSAGE-ID:")) {
char *msgid = HTStrip(full_line + 11);
if (msgid[0] == '<' && msgid[strlen(msgid) - 1] == '>') {
msgid[strlen(msgid) - 1] = '\0'; /* Chop > */
msgid++; /* Chop < */
HTAnchor_setMessageID(thisanchor, msgid);
}
} /* end if match */
p = line; /* Restart at beginning */
} /* if end of line */
} /* Loop over characters */
FREE(full_line);
START(HTML_HEAD);
PUTC('\n');
START(HTML_TITLE);
if (subject && *subject != '\0')
PUTS(subject);
else
PUTS("No Subject");
END(HTML_TITLE);
PUTC('\n');
/*
* Put in the owner as a link rel.
*/
if (from || replyto) {
char *temp = NULL;
StrAllocCopy(temp, author_address(replyto ? replyto : from));
StrAllocCopy(href, STR_MAILTO_URL);
if (StrChr(temp, '%') || StrChr(temp, '?')) {
cp = HTEscape(temp, URL_XPALPHAS);
StrAllocCat(href, cp);
FREE(cp);
} else {
StrAllocCat(href, temp);
}
start_link(href, "made");
PUTC('\n');
FREE(temp);
}
END(HTML_HEAD);
PUTC('\n');
START(HTML_H1);
if (subject && *subject != '\0')
PUTS(subject);
else
PUTS("No Subject");
END(HTML_H1);
PUTC('\n');
if (subject)
FREE(subject);
START(HTML_DLC);
PUTC('\n');
if (from || replyto) {
START(HTML_DT);
START(HTML_B);
PUTS("From:");
END(HTML_B);
PUTC(' ');
if (from)
PUTS(from);
else
PUTS(replyto);
MAYBE_END(HTML_DT);
PUTC('\n');
if (!replyto)
StrAllocCopy(replyto, from);
START(HTML_DT);
START(HTML_B);
PUTS("Reply to:");
END(HTML_B);
PUTC(' ');
start_anchor(href);
if (*replyto != '<')
PUTS(author_name(replyto));
else
PUTS(author_address(replyto));
END(HTML_A);
START(HTML_BR);
MAYBE_END(HTML_DT);
PUTC('\n');
FREE(from);
FREE(replyto);
}
if (date) {
START(HTML_DT);
START(HTML_B);
PUTS("Date:");
END(HTML_B);
PUTC(' ');
PUTS(date);
MAYBE_END(HTML_DT);
PUTC('\n');
FREE(date);
}
if (organization) {
START(HTML_DT);
START(HTML_B);
PUTS("Organization:");
END(HTML_B);
PUTC(' ');
PUTS(organization);
MAYBE_END(HTML_DT);
PUTC('\n');
FREE(organization);
}
/* sanitize some headers - kw */
if (newsgroups &&
((cp = StrChr(newsgroups, '/')) ||
(cp = StrChr(newsgroups, '(')))) {
*cp = '\0';
}
if (newsgroups && !*newsgroups) {
FREE(newsgroups);
}
if (followupto &&
((cp = StrChr(followupto, '/')) ||
(cp = StrChr(followupto, '(')))) {
*cp = '\0';
}
if (followupto && !*followupto) {
FREE(followupto);
}
if (newsgroups && HTCanPost) {
START(HTML_DT);
START(HTML_B);
PUTS("Newsgroups:");
END(HTML_B);
PUTC('\n');
MAYBE_END(HTML_DT);
START(HTML_DD);
write_anchors(newsgroups);
MAYBE_END(HTML_DD);
PUTC('\n');
}
if (followupto && !strcasecomp(followupto, "poster")) {
/*
* "Followup-To: poster" has special meaning. Don't use it to
* construct a newsreply link. -kw
*/
START(HTML_DT);
START(HTML_B);
PUTS("Followup to:");
END(HTML_B);
PUTC(' ');
if (href) {
start_anchor(href);
PUTS("poster");
END(HTML_A);
} else {
PUTS("poster");
}
MAYBE_END(HTML_DT);
PUTC('\n');
FREE(followupto);
}
if (newsgroups && HTCanPost) {
/*
* We have permission to POST to this host, so add a link for
* posting followups for this article. - FM
*/
if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
StrAllocCopy(href, "snewsreply://");
else
StrAllocCopy(href, "newsreply://");
StrAllocCat(href, NewsHost);
StrAllocCat(href, "/");
StrAllocCat(href, (followupto ? followupto : newsgroups));
if (*href == 'n' &&
(ccp = HTAnchor_messageID(thisanchor)) && *ccp) {
StrAllocCat(href, ";ref=");
if (StrChr(ccp, '<') || StrChr(ccp, '&') ||
StrChr(ccp, ' ') || StrChr(ccp, ':') ||
StrChr(ccp, '/') || StrChr(ccp, '%') ||
StrChr(ccp, ';')) {
char *cp1 = HTEscape(ccp, URL_XPALPHAS);
StrAllocCat(href, cp1);
FREE(cp1);
} else {
StrAllocCat(href, ccp);
}
}
START(HTML_DT);
START(HTML_B);
PUTS("Followup to:");
END(HTML_B);
PUTC(' ');
start_anchor(href);
if (StrChr((followupto ? followupto : newsgroups), ',')) {
PUTS("newsgroups");
} else {
PUTS("newsgroup");
}
END(HTML_A);
MAYBE_END(HTML_DT);
PUTC('\n');
}
FREE(newsgroups);
FREE(followupto);
if (references) {
START(HTML_DT);
START(HTML_B);
PUTS("References:");
END(HTML_B);
MAYBE_END(HTML_DT);
PUTC('\n');
START(HTML_DD);
write_anchors(references);
MAYBE_END(HTML_DD);
PUTC('\n');
FREE(references);
}
END(HTML_DLC);
PUTC('\n');
FREE(href);
}
if (rawtext) {
/*
* No tags, and never do a PUTC. - kw
*/
;
} else if (diagnostic) {
/*
* Read in the HEAD and BODY of the Article as XMP formatted text. -
* FM
*/
START(HTML_XMP);
PUTC('\n');
} else {
/*
* Read in the BODY of the Article as PRE formatted text. - FM
*/
START(HTML_PRE);
PUTC('\n');
}
p = line;
while (!done) {
int ich = NEXT_CHAR;
*p++ = (char) ich;
if (ich == EOF) {
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
}
abort_socket(); /* End of file, close socket */
return (HT_LOADED); /* End of file on response */
}
if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
*p = '\0'; /* Terminate the string */
CTRACE((tfp, "B %s", line));
#ifdef NEWS_DEBUG /* 1997/11/09 (Sun) 15:56:11 */
debug_print(line); /* @@@ */
#endif
if (line[0] == '.') {
/*
* End of article?
*/
if (UCH(line[1]) < ' ') {
break;
} else { /* Line starts with dot */
if (rawtext) {
RAW_PUTS(&line[1]);
} else {
PUTS(&line[1]); /* Ignore first dot */
}
}
} else {
if (rawtext) {
RAW_PUTS(line);
} else if (diagnostic || !scan_for_buried_news_references) {
/*
* All lines are passed as unmodified source. - FM
*/
PUTS(line);
} else {
/*
* Normal lines are scanned for buried references to other
* articles. Unfortunately, it could pick up mail
* addresses as well! It also can corrupt uuencoded
* messages! So we don't do this when fetching articles as
* WWW_SOURCE or when downloading (diagnostic is TRUE) or
* if the client has set scan_for_buried_news_references to
* FALSE. Otherwise, we convert all "<...@...>" strings
* preceded by "rticle " to "news:...@..." links, and any
* strings that look like URLs to links. - FM
*/
char *l = line;
char *p2;
while ((p2 = strstr(l, "rticle <")) != NULL) {
char *q = strrchr(p2, '>');
char *at = strrchr(p2, '@');
if (q && at && at < q) {
char c = q[1];
q[1] = 0; /* chop up */
p2 += 7;
*p2 = 0;
while (*l) {
if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
StrNCmp(l, "snews://", 8) &&
StrNCmp(l, "nntp://", 7) &&
StrNCmp(l, "snewspost:", 10) &&
StrNCmp(l, "snewsreply:", 11) &&
StrNCmp(l, "newspost:", 9) &&
StrNCmp(l, "newsreply:", 10) &&
StrNCmp(l, "ftp://", 6) &&
StrNCmp(l, "file:/", 6) &&
StrNCmp(l, "finger://", 9) &&
StrNCmp(l, "http://", 7) &&
StrNCmp(l, "https://", 8) &&
StrNCmp(l, "wais://", 7) &&
StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
StrNCmp(l, "cso://", 6) &&
StrNCmp(l, "gopher://", 9)) {
PUTC(*l++);
} else {
StrAllocCopy(href, l);
start_anchor(strtok(href, " \r\n\t,>)\""));
while (*l && !StrChr(" \r\n\t,>)\"", *l))
PUTC(*l++);
END(HTML_A);
FREE(href);
}
}
*p2 = '<'; /* again */
*q = 0;
start_anchor(p2 + 1);
*q = '>'; /* again */
PUTS(p2);
END(HTML_A);
q[1] = c; /* again */
l = q + 1;
} else {
break; /* line has unmatched <> */
}
}
while (*l) { /* Last bit of the line */
if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
StrNCmp(l, "snews://", 8) &&
StrNCmp(l, "nntp://", 7) &&
StrNCmp(l, "snewspost:", 10) &&
StrNCmp(l, "snewsreply:", 11) &&
StrNCmp(l, "newspost:", 9) &&
StrNCmp(l, "newsreply:", 10) &&
StrNCmp(l, "ftp://", 6) &&
StrNCmp(l, "file:/", 6) &&
StrNCmp(l, "finger://", 9) &&
StrNCmp(l, "http://", 7) &&
StrNCmp(l, "https://", 8) &&
StrNCmp(l, "wais://", 7) &&
StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
StrNCmp(l, "cso://", 6) &&
StrNCmp(l, "gopher://", 9))
PUTC(*l++);
else {
StrAllocCopy(href, l);
start_anchor(strtok(href, " \r\n\t,>)\""));
while (*l && !StrChr(" \r\n\t,>)\"", *l))
PUTC(*l++);
END(HTML_A);
FREE(href);
}
}
} /* if diagnostic or not scan_for_buried_news_references */
} /* if not dot */
p = line; /* Restart at beginning */
} /* if end of line */
} /* Loop over characters */
if (rawtext)
return (HT_LOADED);
if (diagnostic)
END(HTML_XMP);
else
END(HTML_PRE);
PUTC('\n');
return (HT_LOADED);
}
/* Read in a List of Newsgroups
* ----------------------------
*
* Note the termination condition of a single dot on a line by itself.
* RFC 977 specifies that the line "folding" of RFC850 is not used,
* so we do not handle it here.
*/
static int read_list(char *arg)
{
char line[LINE_LENGTH + 1];
char *p;
BOOL done = NO;
BOOL head = NO;
BOOL tail = NO;
BOOL skip_this_line = NO;
BOOL skip_rest_of_line = NO;
int listing = 0;
char *pattern = NULL;
int len = 0;
/*
* Support head or tail matches for groups to list. - FM
*/
if (arg && strlen(arg) > 1) {
if (*arg == '*') {
tail = YES;
StrAllocCopy(pattern, (arg + 1));
} else if (arg[strlen(arg) - 1] == '*') {
head = YES;
StrAllocCopy(pattern, arg);
pattern[strlen(pattern) - 1] = '\0';
}
if (tail || head) {
len = (int) strlen(pattern);
}
}
/*
* Read the server's reply.
*
* The lines are scanned for newsgroup names and descriptions.
*/
START(HTML_HEAD);
PUTC('\n');
START(HTML_TITLE);
PUTS("Newsgroups");
END(HTML_TITLE);
PUTC('\n');
END(HTML_HEAD);
PUTC('\n');
START(HTML_H1);
PUTS("Newsgroups");
END(HTML_H1);
PUTC('\n');
p = line;
START(HTML_DLC);
PUTC('\n');
while (!done) {
int ich = NEXT_CHAR;
char ch = (char) ich;
if (ich == EOF) {
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
}
abort_socket(); /* End of file, close socket */
FREE(pattern);
return (HT_LOADED); /* End of file on response */
} else if (skip_this_line) {
if (ch == LF) {
skip_this_line = skip_rest_of_line = NO;
p = line;
}
continue;
} else if (skip_rest_of_line) {
if (ch != LF) {
continue;
}
} else if (p == &line[LINE_LENGTH]) {
CTRACE((tfp, "b %.*s%c[...]\n", (LINE_LENGTH), line, ch));
*p = '\0';
if (ch == LF) {
; /* Will be dealt with below */
} else if (WHITE(ch)) {
ch = LF; /* May treat as line without description */
skip_this_line = YES; /* ...and ignore until LF */
} else if (StrChr(line, ' ') == NULL &&
StrChr(line, '\t') == NULL) {
/* No separator found */
CTRACE((tfp, "HTNews..... group name too long, discarding.\n"));
skip_this_line = YES; /* ignore whole line */
continue;
} else {
skip_rest_of_line = YES; /* skip until ch == LF found */
}
} else {
*p++ = ch;
}
if (ch == LF) {
skip_rest_of_line = NO; /* done, reset flag */
*p = '\0'; /* Terminate the string */
CTRACE((tfp, "B %s", line));
if (line[0] == '.') {
/*
* End of article?
*/
if (UCH(line[1]) < ' ') {
break;
} else { /* Line starts with dot */
START(HTML_DT);
PUTS(&line[1]);
MAYBE_END(HTML_DT);
}
} else if (line[0] == '#') { /* Comment? */
p = line; /* Restart at beginning */
continue;
} else {
/*
* Normal lines are scanned for references to newsgroups.
*/
int i = 0;
/* find whitespace if it exits */
for (; line[i] != '\0' && !WHITE(line[i]); i++) ; /* null body */
if (line[i] != '\0') {
line[i] = '\0';
if ((head && strncasecomp(line, pattern, len)) ||
(tail && (i < len ||
strcasecomp((line + (i - len)), pattern)))) {
p = line; /* Restart at beginning */
continue;
}
START(HTML_DT);
write_anchor(line, line);
listing++;
MAYBE_END(HTML_DT);
PUTC('\n');
START(HTML_DD);
PUTS(&line[i + 1]); /* put description */
MAYBE_END(HTML_DD);
} else {
if ((head && strncasecomp(line, pattern, len)) ||
(tail && (i < len ||
strcasecomp((line + (i - len)), pattern)))) {
p = line; /* Restart at beginning */
continue;
}
START(HTML_DT);
write_anchor(line, line);
MAYBE_END(HTML_DT);
listing++;
}
} /* if not dot */
p = line; /* Restart at beginning */
} /* if end of line */
} /* Loop over characters */
if (!listing) {
char *msg = NULL;
START(HTML_DT);
HTSprintf0(&msg, gettext("No matches for: %s"), arg);
PUTS(msg);
MAYBE_END(HTML_DT);
FREE(msg);
}
END(HTML_DLC);
PUTC('\n');
FREE(pattern);
return (HT_LOADED);
}
/* Read in a Newsgroup
* -------------------
*
* Unfortunately, we have to ask for each article one by one if we
* want more than one field.
*
*/
static int read_group(const char *groupName,
int first_required,
int last_required)
{
char line[LINE_LENGTH + 1];
char *author = NULL;
char *subject = NULL;
char *date = NULL;
int i;
char *p;
BOOL done;
char buffer[LINE_LENGTH + 1];
char *temp = NULL;
char *reference = NULL; /* Href for article */
int art; /* Article number WITHIN GROUP */
int status, count, first, last; /* Response fields */
START(HTML_HEAD);
PUTC('\n');
START(HTML_TITLE);
PUTS("Newsgroup ");
PUTS(groupName);
END(HTML_TITLE);
PUTC('\n');
END(HTML_HEAD);
PUTC('\n');
sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
CTRACE((tfp, "Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
status, count, first, last, first_required, last_required));
if (last == 0) {
PUTS(gettext("\nNo articles in this group.\n"));
goto add_post;
}
#define FAST_THRESHOLD 100 /* Above this, read IDs fast */
#define CHOP_THRESHOLD 50 /* Above this, chop off the rest */
if (first_required < first)
first_required = first; /* clip */
if ((last_required == 0) || (last_required > last))
last_required = last;
if (last_required < first_required) {
PUTS(gettext("\nNo articles in this range.\n"));
goto add_post;
}
if (last_required - first_required + 1 > HTNewsMaxChunk) { /* Trim this block */
first_required = last_required - HTNewsChunkSize + 1;
}
CTRACE((tfp, " Chunk will be (%d-%d)\n",
first_required, last_required));
/*
* Set window title.
*/
HTSprintf0(&temp, gettext("%s, Articles %d-%d"),
groupName, first_required, last_required);
START(HTML_H1);
PUTS(temp);
FREE(temp);
END(HTML_H1);
PUTC('\n');
/*
* Link to earlier articles.
*/
if (first_required > first) {
int before; /* Start of one before */
if (first_required - HTNewsMaxChunk <= first)
before = first;
else
before = first_required - HTNewsChunkSize;
HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
before, first_required - 1);
CTRACE((tfp, " Block before is %s\n", dbuf));
PUTC('(');
start_anchor(dbuf);
PUTS(gettext("Earlier articles"));
END(HTML_A);
PUTS("...)\n");
START(HTML_P);
PUTC('\n');
}
done = NO;
/*#define USE_XHDR*/
#ifdef USE_XHDR
if (count > FAST_THRESHOLD) {
HTSprintf0(&temp,
gettext("\nThere are about %d articles currently available in %s, IDs as follows:\n\n"),
count, groupName);
PUTS(temp);
FREE(temp);
sprintf(buffer, "XHDR Message-ID %d-%d%c%c", first, last, CR, LF);
status = response(buffer);
if (status == 221) {
p = line;
while (!done) {
int ich = NEXT_CHAR;
*p++ = ich;
if (ich == EOF) {
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
}
abort_socket(); /* End of file, close socket */
return (HT_LOADED); /* End of file on response */
}
if (((char) ich == '\n') || (p == &line[LINE_LENGTH])) {
*p = '\0'; /* Terminate the string */
CTRACE((tfp, "X %s", line));
if (line[0] == '.') {
/*
* End of article?
*/
if (UCH(line[1]) < ' ') {
done = YES;
break;
} else { /* Line starts with dot */
/* Ignore strange line */
}
} else {
/*
* Normal lines are scanned for references to articles.
*/
char *space = StrChr(line, ' ');
if (space++)
write_anchor(space, space);
} /* if not dot */
p = line; /* Restart at beginning */
} /* if end of line */
} /* Loop over characters */
/* leaving loop with "done" set */
} /* Good status */
}
#endif /* USE_XHDR */
/*
* Read newsgroup using individual fields.
*/
if (!done) {
START(HTML_B);
if (first == first_required && last == last_required)
PUTS(gettext("All available articles in "));
else
PUTS("Articles in ");
PUTS(groupName);
END(HTML_B);
PUTC('\n');
if (LYListNewsNumbers)
start_list(first_required);
else
START(HTML_UL);
for (art = first_required; art <= last_required; art++) {
/*#define OVERLAP*/
#ifdef OVERLAP
/*
* With this code we try to keep the server running flat out by
* queuing just one extra command ahead of time. We assume (1)
* that the server won't abort if it gets input during output, and
* (2) that TCP buffering is enough for the two commands. Both
* these assumptions seem very reasonable. However, we HAVE had a
* hangup with a loaded server.
*/
if (art == first_required) {
if (art == last_required) { /* Only one */
sprintf(buffer, "HEAD %d%c%c",
art, CR, LF);
status = response(buffer);
} else { /* First of many */
sprintf(buffer, "HEAD %d%c%cHEAD %d%c%c",
art, CR, LF, art + 1, CR, LF);
status = response(buffer);
}
} else if (art == last_required) { /* Last of many */
status = response(NULL);
} else { /* Middle of many */
sprintf(buffer, "HEAD %d%c%c", art + 1, CR, LF);
status = response(buffer);
}
#else /* Not OVERLAP: */
sprintf(buffer, "HEAD %d%c%c", art, CR, LF);
status = response(buffer);
#endif /* OVERLAP */
/*
* Check for a good response (221) for the HEAD request, and if so,
* parse it. Otherwise, indicate the error so that the number of
* listings corresponds to what's claimed for the range, and if we
* are listing numbers via an ordered list, they stay in synchrony
* with the article numbers. - FM
*/
if (status == 221) { /* Head follows - parse it: */
p = line; /* Write pointer */
done = NO;
while (!done) {
int ich = NEXT_CHAR;
*p++ = (char) ich;
if (ich == EOF) {
if (interrupted_in_htgetcharacter) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
}
abort_socket(); /* End of file, close socket */
return (HT_LOADED); /* End of file on response */
}
if (((char) ich == LF) ||
(p == &line[LINE_LENGTH])) {
*--p = '\0'; /* Terminate & chop LF */
p = line; /* Restart at beginning */
CTRACE((tfp, "G %s\n", line));
switch (line[0]) {
case '.':
/*
* End of article?
*/
done = (BOOL) (UCH(line[1]) < ' ');
break;
case 'S':
case 's':
if (match(line, "SUBJECT:")) {
StrAllocCopy(subject, line + 9);
decode_mime(&subject);
}
break;
case 'M':
case 'm':
if (match(line, "MESSAGE-ID:")) {
char *addr = HTStrip(line + 11) + 1; /* Chop < */
addr[strlen(addr) - 1] = '\0'; /* Chop > */
StrAllocCopy(reference, addr);
}
break;
case 'f':
case 'F':
if (match(line, "FROM:")) {
char *p2;
StrAllocCopy(author, StrChr(line, ':') + 1);
decode_mime(&author);
p2 = author + strlen(author) - 1;
if (*p2 == LF)
*p2 = '\0'; /* Chop off newline */
}
break;
case 'd':
case 'D':
if (LYListNewsDates && match(line, "DATE:")) {
StrAllocCopy(date,
HTStrip(StrChr(line, ':') + 1));
}
break;
} /* end switch on first character */
} /* if end of line */
} /* Loop over characters */
PUTC('\n');
START(HTML_LI);
p = decode_mime(&subject);
HTSprintf0(&temp, "\"%s\"", NonNull(p));
if (reference) {
write_anchor(temp, reference);
FREE(reference);
} else {
PUTS(temp);
}
FREE(temp);
if (author != NULL) {
PUTS(" - ");
if (LYListNewsDates)
START(HTML_I);
PUTS(decode_mime(&author));
if (LYListNewsDates)
END(HTML_I);
FREE(author);
}
if (date) {
if (!diagnostic) {
for (i = 0; date[i]; i++) {
if (date[i] == ' ') {
date[i] = HT_NON_BREAK_SPACE;
}
}
}
sprintf(buffer, " [%.*s]", (int) (sizeof(buffer) - 4), date);
PUTS(buffer);
FREE(date);
}
MAYBE_END(HTML_LI);
/*
* Indicate progress! @@@@@@
*/
} else if (status == HT_INTERRUPTED) {
interrupted_in_htgetcharacter = 0;
CTRACE((tfp,
"HTNews: Interrupted on read, closing socket %d\n",
s));
NEWS_NETCLOSE(s);
s = -1;
return (HT_INTERRUPTED);
} else {
/*
* Use the response text on error. - FM
*/
PUTC('\n');
START(HTML_LI);
START(HTML_I);
if (LYListNewsNumbers)
LYStrNCpy(buffer, "Status:", sizeof(buffer) - 1);
else
sprintf(buffer, "Status (ARTICLE %d):", art);
PUTS(buffer);
END(HTML_I);
PUTC(' ');
PUTS(response_text);
MAYBE_END(HTML_LI);
} /* Handle response to HEAD request */
} /* Loop over article */
FREE(author);
FREE(subject);
} /* If read headers */
PUTC('\n');
if (LYListNewsNumbers)
END(HTML_OL);
else
END(HTML_UL);
PUTC('\n');
/*
* Link to later articles.
*/
if (last_required < last) {
int after; /* End of article after */
after = last_required + HTNewsChunkSize;
if (after == last)
HTSprintf0(&dbuf, "%s%s", NewsHREF, groupName); /* original group */
else
HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
last_required + 1, after);
CTRACE((tfp, " Block after is %s\n", dbuf));
PUTC('(');
start_anchor(dbuf);
PUTS(gettext("Later articles"));
END(HTML_A);
PUTS("...)\n");
}
add_post:
if (HTCanPost) {
/*
* We have permission to POST to this host, so add a link for posting
* messages to this newsgroup. - FM
*/
char *href = NULL;
START(HTML_HR);
PUTC('\n');
if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
StrAllocCopy(href, "snewspost://");
else
StrAllocCopy(href, "newspost://");
StrAllocCat(href, NewsHost);
StrAllocCat(href, "/");
StrAllocCat(href, groupName);
start_anchor(href);
PUTS(gettext("Post to "));
PUTS(groupName);
END(HTML_A);
FREE(href);
} else {
START(HTML_HR);
}
PUTC('\n');
return (HT_LOADED);
}
/* Load by name. HTLoadNews
* =============
*/
static int HTLoadNews(const char *arg,
HTParentAnchor *anAnchor,
HTFormat format_out,
HTStream *stream)
{
char command[262]; /* The whole command */
char proxycmd[260]; /* The proxy command */
char groupName[GROUP_NAME_LENGTH]; /* Just the group name */
int status; /* tcp return */
int retries; /* A count of how hard we have tried */
BOOL normal_url; /* Flag: "news:" or "nntp:" (physical) URL */
BOOL group_wanted; /* Flag: group was asked for, not article */
BOOL list_wanted; /* Flag: list was asked for, not article */
BOOL post_wanted; /* Flag: new post to group was asked for */
BOOL reply_wanted; /* Flag: followup post was asked for */
BOOL spost_wanted; /* Flag: new SSL post to group was asked for */
BOOL sreply_wanted; /* Flag: followup SSL post was asked for */
BOOL head_wanted = NO; /* Flag: want HEAD of single article */
int first, last; /* First and last articles asked for */
char *cp = 0;
char *ListArg = NULL;
char *ProxyHost = NULL;
char *ProxyHREF = NULL;
char *postfile = NULL;
#ifdef USE_SSL
char SSLprogress[256];
#endif /* USE_SSL */
diagnostic = (format_out == WWW_SOURCE || /* set global flag */
format_out == HTAtom_for("www/download") ||
format_out == HTAtom_for("www/dump"));
rawtext = NO;
CTRACE((tfp, "HTNews: Looking for %s\n", arg));
if (!initialized)
initialized = initialize();
if (!initialized)
return -1; /* FAIL */
FREE(NewsHREF);
command[0] = '\0';
command[sizeof(command) - 1] = '\0';
proxycmd[0] = '\0';
proxycmd[sizeof(proxycmd) - 1] = '\0';
{
const char *p1;
/*
* We will ask for the document, omitting the host name & anchor.
*
* Syntax of address is
* xxx@yyy Article
* <xxx@yyy> Same article
* xxxxx News group (no "@")
* group/n1-n2 Articles n1 to n2 in group
*/
normal_url = (BOOL) (!StrNCmp(arg, STR_NEWS_URL, LEN_NEWS_URL) ||
!StrNCmp(arg, "nntp:", 5));
spost_wanted = (BOOL) (!normal_url && strstr(arg, "snewspost:") != NULL);
sreply_wanted = (BOOL) (!(normal_url || spost_wanted) &&
strstr(arg, "snewsreply:") != NULL);
post_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted) &&
strstr(arg, "newspost:") != NULL);
reply_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted ||
post_wanted) &&
strstr(arg, "newsreply:") != NULL);
group_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
post_wanted || reply_wanted) &&
StrChr(arg, '@') == NULL) &&
(StrChr(arg, '*') == NULL));
list_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
post_wanted || reply_wanted ||
group_wanted) &&
StrChr(arg, '@') == NULL) &&
(StrChr(arg, '*') != NULL));
#ifndef USE_SSL
if (!strncasecomp(arg, "snewspost:", 10) ||
!strncasecomp(arg, "snewsreply:", 11)) {
HTAlert(FAILED_CANNOT_POST_SSL);
return HT_NOT_LOADED;
}
#endif /* !USE_SSL */
if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
/*
* Make sure we have a non-zero path for the newsgroup(s). - FM
*/
if ((p1 = strrchr(arg, '/')) != NULL) {
p1++;
} else if ((p1 = strrchr(arg, ':')) != NULL) {
p1++;
}
if (!(p1 && *p1)) {
HTAlert(WWW_ILLEGAL_URL_MESSAGE);
return (HT_NO_DATA);
}
if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, HTNewsHost);
} else {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, cp);
}
FREE(cp);
HTSprintf0(&NewsHREF, "%s://%.*s/",
(post_wanted ?
"newspost" :
(reply_wanted ?
"newreply" :
(spost_wanted ?
"snewspost" : "snewsreply"))),
(int) sizeof(command) - 15, NewsHost);
/*
* If the SSL daemon is being used as a proxy, reset p1 to the
* start of the proxied URL rather than to the start of the
* newsgroup(s). - FM
*/
if (spost_wanted && strncasecomp(arg, "snewspost:", 10))
p1 = strstr(arg, "snewspost:");
if (sreply_wanted && strncasecomp(arg, "snewsreply:", 11))
p1 = strstr(arg, "snewsreply:");
/* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
/*
* Don't use HTParse because news: access doesn't follow
* traditional rules. For instance, if the article reference
* contains a '#', the rest of it is lost -- JFG 10/7/92, from a
* bug report
*/
} else if (isNNTP_URL(arg)) {
if (((*(arg + 5) == '\0') ||
(!strcmp((arg + 5), "/") ||
!strcmp((arg + 5), "//") ||
!strcmp((arg + 5), "///"))) ||
((!StrNCmp((arg + 5), "//", 2)) &&
(!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
p1 = "*";
group_wanted = FALSE;
list_wanted = TRUE;
} else if (*(arg + 5) != '/') {
p1 = (arg + 5);
} else if (*(arg + 5) == '/' && *(arg + 6) != '/') {
p1 = (arg + 6);
} else {
p1 = (cp + 1);
}
if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, HTNewsHost);
} else {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, cp);
}
FREE(cp);
SnipIn2(command, "%s//%.*s/", STR_NNTP_URL, 9, NewsHost);
StrAllocCopy(NewsHREF, command);
} else if (!strncasecomp(arg, STR_SNEWS_URL, 6)) {
#ifdef USE_SSL
if (((*(arg + 6) == '\0') ||
(!strcmp((arg + 6), "/") ||
!strcmp((arg + 6), "//") ||
!strcmp((arg + 6), "///"))) ||
((!StrNCmp((arg + 6), "//", 2)) &&
(!(cp = StrChr((arg + 8), '/')) || *(cp + 1) == '\0'))) {
p1 = "*";
group_wanted = FALSE;
list_wanted = TRUE;
} else if (*(arg + 6) != '/') {
p1 = (arg + 6);
} else if (*(arg + 6) == '/' && *(arg + 7) != '/') {
p1 = (arg + 7);
} else {
p1 = (cp + 1);
}
if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, HTNewsHost);
} else {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, cp);
}
FREE(cp);
sprintf(command, "%s//%.250s/", STR_SNEWS_URL, NewsHost);
StrAllocCopy(NewsHREF, command);
#else
HTAlert(gettext("This client does not contain support for SNEWS URLs."));
return HT_NOT_LOADED;
#endif /* USE_SSL */
} else if (!strncasecomp(arg, "news:/", 6)) {
if (((*(arg + 6) == '\0') ||
!strcmp((arg + 6), "/") ||
!strcmp((arg + 6), "//")) ||
((*(arg + 6) == '/') &&
(!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
p1 = "*";
group_wanted = FALSE;
list_wanted = TRUE;
} else if (*(arg + 6) != '/') {
p1 = (arg + 6);
} else {
p1 = (cp + 1);
}
if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, HTNewsHost);
} else {
if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, cp);
}
FREE(cp);
SnipIn(command, "news://%.*s/", 9, NewsHost);
StrAllocCopy(NewsHREF, command);
} else {
p1 = (arg + 5); /* Skip "news:" prefix */
if (*p1 == '\0') {
p1 = "*";
group_wanted = FALSE;
list_wanted = TRUE;
}
if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
NEWS_NETCLOSE(s);
s = -1;
}
StrAllocCopy(NewsHost, HTNewsHost);
StrAllocCopy(NewsHREF, STR_NEWS_URL);
}
/*
* Set up any proxy for snews URLs that returns NNTP responses for Lynx
* to convert to HTML, instead of doing the conversion itself, and for
* handling posts or followups. - TZ & FM
*/
if (!strncasecomp(p1, STR_SNEWS_URL, 6) ||
!strncasecomp(p1, "snewspost:", 10) ||
!strncasecomp(p1, "snewsreply:", 11)) {
StrAllocCopy(ProxyHost, NewsHost);
if ((cp = HTParse(p1, "", PARSE_HOST)) != NULL && *cp != '\0') {
SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, cp);
StrAllocCopy(NewsHost, cp);
} else {
SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, NewsHost);
}
command[sizeof(command) - 2] = '\0';
FREE(cp);
sprintf(proxycmd, "GET %.*s%c%c%c%c",
(int) sizeof(proxycmd) - 9, command,
CR, LF, CR, LF);
CTRACE((tfp, "HTNews: Proxy command is '%.*s'\n",
(int) (strlen(proxycmd) - 4), proxycmd));
strcat(command, "/");
StrAllocCopy(ProxyHREF, NewsHREF);
StrAllocCopy(NewsHREF, command);
if (spost_wanted || sreply_wanted) {
/*
* Reset p1 so that it points to the newsgroup(s).
*/
if ((p1 = strrchr(arg, '/')) != NULL) {
p1++;
} else {
p1 = (strrchr(arg, ':') + 1);
}
} else {
/*
* Reset p1 so that it points to the newsgroup (or a wildcard),
* or the article.
*/
if (!(cp = strrchr((p1 + 6), '/')) || *(cp + 1) == '\0') {
p1 = "*";
group_wanted = FALSE;
list_wanted = TRUE;
} else {
p1 = (cp + 1);
}
}
}
/*
* Set up command for a post, listing, or article request. - FM
*/
if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
strcpy(command, "POST");
} else if (list_wanted) {
if (strlen(p1) > 249) {
FREE(ProxyHost);
FREE(ProxyHREF);
HTAlert(URL_TOO_LONG);
return -400;
}
SnipIn(command, "XGTITLE %.*s", 11, p1);
} else if (group_wanted) {
char *slash = StrChr(p1, '/');
first = 0;
last = 0;
if (slash) {
*slash = '\0';
if (strlen(p1) >= sizeof(groupName)) {
FREE(ProxyHost);
FREE(ProxyHREF);
HTAlert(URL_TOO_LONG);
return -400;
}
LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
*slash = '/';
(void) sscanf(slash + 1, "%d-%d", &first, &last);
if ((first > 0) && (isdigit(UCH(*(slash + 1)))) &&
(StrChr(slash + 1, '-') == NULL || first == last)) {
/*
* We got a number greater than 0, which will be loaded as
* first, and either no range or the range computes to
* zero, so make last negative, as a flag to select the
* group and then fetch an article by number (first)
* instead of by messageID. - FM
*/
last = -1;
}
} else {
if (strlen(p1) >= sizeof(groupName)) {
FREE(ProxyHost);
FREE(ProxyHREF);
HTAlert(URL_TOO_LONG);
return -400;
}
LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
}
SnipIn(command, "GROUP %.*s", 9, groupName);
} else {
size_t add_open = (size_t) (StrChr(p1, '<') == 0);
size_t add_close = (size_t) (StrChr(p1, '>') == 0);
if (strlen(p1) + add_open + add_close >= 252) {
FREE(ProxyHost);
FREE(ProxyHREF);
HTAlert(URL_TOO_LONG);
return -400;
}
sprintf(command, "ARTICLE %s%.*s%s",
add_open ? "<" : "",
(int) (sizeof(command) - (11 + add_open + add_close)),
p1,
add_close ? ">" : "");
}
{
char *p = command + strlen(command);
/*
* Terminate command with CRLF, as in RFC 977.
*/
*p++ = CR; /* Macros to be correct on Mac */
*p++ = LF;
*p = 0;
}
StrAllocCopy(ListArg, p1);
} /* scope of p1 */
if (!*arg) {
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
return NO; /* Ignore if no name */
}
if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted ||
(group_wanted && last != -1) || list_wanted)) {
head_wanted = anAnchor->isHEAD;
if (head_wanted && !StrNCmp(command, "ARTICLE ", 8)) {
/* overwrite "ARTICLE" - hack... */
strcpy(command, "HEAD ");
for (cp = command + 5;; cp++)
if ((*cp = *(cp + 3)) == '\0')
break;
}
rawtext = (BOOL) (head_wanted || keep_mime_headers);
}
if (rawtext) {
rawtarget = HTStreamStack(WWW_PLAINTEXT,
format_out,
stream, anAnchor);
if (!rawtarget) {
FREE(NewsHost);
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
HTAlert(gettext("No target for raw text!"));
return (HT_NOT_LOADED);
} /* Copy routine entry points */
rawtargetClass = *rawtarget->isa;
} else
/*
* Make a hypertext object with an anchor list.
*/
if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted)) {
target = HTML_new(anAnchor, format_out, stream);
targetClass = *target->isa; /* Copy routine entry points */
}
/*
* Now, let's get a stream setup up from the NewsHost.
*/
for (retries = 0; retries < 2; retries++) {
if (s < 0) {
/* CONNECTING to news host */
char url[260];
if (!strcmp(NewsHREF, STR_NEWS_URL)) {
SnipIn(url, "lose://%.*s/", 9, NewsHost);
} else if (ProxyHREF) {
SnipIn(url, "%.*s", 1, ProxyHREF);
} else {
SnipIn(url, "%.*s", 1, NewsHREF);
}
CTRACE((tfp, "News: doing HTDoConnect on '%s'\n", url));
_HTProgress(gettext("Connecting to NewsHost ..."));
#ifdef USE_SSL
if (!using_proxy &&
(!StrNCmp(arg, STR_SNEWS_URL, 6) ||
!StrNCmp(arg, "snewspost:", 10) ||
!StrNCmp(arg, "snewsreply:", 11)))
status = HTDoConnect(url, "NNTPS", SNEWS_PORT, &s);
else
status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
#else
status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
#endif /* USE_SSL */
if (status == HT_INTERRUPTED) {
/*
* Interrupt cleanly.
*/
CTRACE((tfp,
"HTNews: Interrupted on connect; recovering cleanly.\n"));
_HTProgress(CONNECTION_INTERRUPTED);
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
ABORT_TARGET;
}
FREE(NewsHost);
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
#ifdef USE_SSL
if (Handle) {
SSL_free(Handle);
Handle = NULL;
}
#endif /* USE_SSL */
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return HT_NOT_LOADED;
}
if (status < 0) {
NEWS_NETCLOSE(s);
s = -1;
CTRACE((tfp, "HTNews: Unable to connect to news host.\n"));
if (retries < 1)
continue;
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
ABORT_TARGET;
}
HTSprintf0(&dbuf, gettext("Could not access %s."), NewsHost);
FREE(NewsHost);
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return HTLoadError(stream, 500, dbuf);
} else {
CTRACE((tfp, "HTNews: Connected to news host %s.\n",
NewsHost));
#ifdef USE_SSL
/*
* If this is an snews url, then do the SSL stuff here
*/
if (!using_proxy &&
(!StrNCmp(url, "snews", 5) ||
!StrNCmp(url, "snewspost:", 10) ||
!StrNCmp(url, "snewsreply:", 11))) {
Handle = HTGetSSLHandle();
SSL_set_fd(Handle, s);
HTSSLInitPRNG();
status = SSL_connect(Handle);
if (status <= 0) {
unsigned long SSLerror;
CTRACE((tfp,
"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
url, status));
SSL_load_error_strings();
while ((SSLerror = ERR_get_error()) != 0) {
CTRACE((tfp, "HTNews: SSL: %s\n",
ERR_error_string(SSLerror, NULL)));
}
HTAlert("Unable to make secure connection to remote host.");
NEWS_NETCLOSE(s);
s = -1;
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted))
(*targetClass._abort) (target, NULL);
FREE(NewsHost);
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
if (postfile) {
#ifdef VMS
while (remove(postfile) == 0) ; /* loop through all versions */
#else
remove(postfile);
#endif /* VMS */
FREE(postfile);
}
return HT_NOT_LOADED;
}
sprintf(SSLprogress,
"Secure %d-bit %s (%s) NNTP connection",
SSL_get_cipher_bits(Handle, NULL),
SSL_get_cipher_version(Handle),
SSL_get_cipher(Handle));
_HTProgress(SSLprogress);
}
#endif /* USE_SSL */
HTInitInput(s); /* set up buffering */
if (proxycmd[0]) {
status = (int) NEWS_NETWRITE(s, proxycmd, (int) strlen(proxycmd));
CTRACE((tfp,
"HTNews: Proxy command returned status '%d'.\n",
status));
}
if (((status = response(NULL)) / 100) != 2) {
NEWS_NETCLOSE(s);
s = -1;
if (status == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
ABORT_TARGET;
}
FREE(NewsHost);
FREE(NewsHREF);
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return (HT_NOT_LOADED);
}
if (retries < 1)
continue;
FREE(ProxyHost);
FREE(ProxyHREF);
FREE(ListArg);
FREE(postfile);
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
ABORT_TARGET;
}
if (response_text[0]) {
HTSprintf0(&dbuf,
gettext("Can't read news info. News host %.20s responded: %.200s"),
NewsHost, response_text);
} else {
HTSprintf0(&dbuf,
gettext("Can't read news info, empty response from host %s"),
NewsHost);
}
return HTLoadError(stream, 500, dbuf);
}
if (status == 200) {
HTCanPost = TRUE;
} else {
HTCanPost = FALSE;
if (post_wanted || reply_wanted ||
spost_wanted || sreply_wanted) {
HTAlert(CANNOT_POST);
FREE(NewsHREF);
if (ProxyHREF) {
StrAllocCopy(NewsHost, ProxyHost);
FREE(ProxyHost);
FREE(ProxyHREF);
}
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return (HT_NOT_LOADED);
}
}
}
}
/* If needed opening */
if (post_wanted || reply_wanted ||
spost_wanted || sreply_wanted) {
if (!HTCanPost) {
HTAlert(CANNOT_POST);
FREE(NewsHREF);
if (ProxyHREF) {
StrAllocCopy(NewsHost, ProxyHost);
FREE(ProxyHost);
FREE(ProxyHREF);
}
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return (HT_NOT_LOADED);
}
if (postfile == NULL) {
postfile = LYNewsPost(ListArg,
(reply_wanted || sreply_wanted));
}
if (postfile == NULL) {
HTProgress(CANCELLED);
FREE(NewsHREF);
if (ProxyHREF) {
StrAllocCopy(NewsHost, ProxyHost);
FREE(ProxyHost);
FREE(ProxyHREF);
}
FREE(ListArg);
return (HT_NOT_LOADED);
}
} else {
/*
* Ensure reader mode, but don't bother checking the status for
* anything but HT_INTERRUPTED or a 480 Authorization request,
* because if the reader mode command is not needed, the server
* probably returned a 500, which is irrelevant at this point. -
* FM
*/
char buffer[20];
sprintf(buffer, "mode reader%c%c", CR, LF);
if ((status = response(buffer)) == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
break;
}
if (status == 480) {
NNTPAuthResult auth_result = HTHandleAuthInfo(NewsHost);
if (auth_result == NNTPAUTH_CLOSE) {
if (s != -1 && !(ProxyHost || ProxyHREF)) {
NEWS_NETCLOSE(s);
s = -1;
}
}
if (auth_result != NNTPAUTH_OK) {
break;
}
if (response(buffer) == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
break;
}
}
}
Send_NNTP_command:
#ifdef NEWS_DEB
if (postfile)
printf("postfile = %s, command = %s", postfile, command);
else
printf("command = %s", command);
#endif
if ((status = response(command)) == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
break;
}
if (status < 0) {
if (retries < 1) {
continue;
} else {
break;
}
}
/*
* For some well known error responses which are expected to occur in
* normal use, break from the loop without retrying and without closing
* the connection. It is unlikely that these are leftovers from a
* timed-out connection (but we do some checks to see whether the
* response corresponds to the last command), or that they will give
* anything else when automatically retried. - kw
*/
if (status == 411 && group_wanted &&
!StrNCmp(command, "GROUP ", 6) &&
!strncasecomp(response_text + 3, " No such group ", 15) &&
!strcmp(response_text + 18, groupName)) {
HTAlert(response_text);
break;
} else if (status == 430 && !group_wanted && !list_wanted &&
!StrNCmp(command, "ARTICLE <", 9) &&
!strcasecomp(response_text + 3, " No such article")) {
HTAlert(response_text);
break;
}
if ((status / 100) != 2 &&
status != 340 &&
status != 480) {
if (retries) {
if (list_wanted && !StrNCmp(command, "XGTITLE", 7)) {
sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
goto Send_NNTP_command;
}
HTAlert(response_text);
} else {
_HTProgress(response_text);
}
NEWS_NETCLOSE(s);
s = -1;
/*
* Message might be a leftover "Timeout-disconnected", so try again
* if the retries maximum has not been reached.
*/
continue;
}
/*
* Post or load a group, article, etc
*/
if (status == 480) {
NNTPAuthResult auth_result;
/*
* Some servers return 480 for a failed XGTITLE. - FM
*/
if (list_wanted && !StrNCmp(command, "XGTITLE", 7) &&
strstr(response_text, "uthenticat") == NULL &&
strstr(response_text, "uthor") == NULL) {
sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
goto Send_NNTP_command;
}
/*
* Handle Authorization. - FM
*/
if ((auth_result = HTHandleAuthInfo(NewsHost)) == NNTPAUTH_OK) {
goto Send_NNTP_command;
} else if (auth_result == NNTPAUTH_CLOSE) {
if (s != -1 && !(ProxyHost || ProxyHREF)) {
NEWS_NETCLOSE(s);
s = -1;
}
if (retries < 1)
continue;
}
status = HT_NOT_LOADED;
} else if (post_wanted || reply_wanted ||
spost_wanted || sreply_wanted) {
/*
* Handle posting of an article. - FM
*/
if (status != 340) {
HTAlert(CANNOT_POST);
if (postfile) {
HTSYS_remove(postfile);
}
} else {
post_article(postfile);
}
FREE(postfile);
status = HT_NOT_LOADED;
} else if (list_wanted) {
/*
* List available newsgroups. - FM
*/
_HTProgress(gettext("Reading list of available newsgroups."));
status = read_list(ListArg);
} else if (group_wanted) {
/*
* List articles in a news group. - FM
*/
if (last < 0) {
/*
* We got one article number rather than a range following the
* slash which followed the group name, or the range was zero,
* so now that we have selected that group, load ARTICLE and
* the the number (first) as the command and go back to send it
* and check the response. - FM
*/
sprintf(command, "%s %d%c%c",
head_wanted ? "HEAD" : "ARTICLE",
first, CR, LF);
group_wanted = FALSE;
retries = 2;
goto Send_NNTP_command;
}
_HTProgress(gettext("Reading list of articles in newsgroup."));
status = read_group(groupName, first, last);
} else {
/*
* Get an article from a news group. - FM
*/
_HTProgress(gettext("Reading news article."));
status = read_article(anAnchor);
}
if (status == HT_INTERRUPTED) {
_HTProgress(CONNECTION_INTERRUPTED);
status = HT_LOADED;
}
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
if (status == HT_NOT_LOADED) {
ABORT_TARGET;
} else {
FREE_TARGET;
}
}
FREE(NewsHREF);
if (ProxyHREF) {
StrAllocCopy(NewsHost, ProxyHost);
FREE(ProxyHost);
FREE(ProxyHREF);
}
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return status;
} /* Retry loop */
#if 0
HTAlert(gettext("Sorry, could not load requested news."));
NXRunAlertPanel(NULL, "Sorry, could not load `%s'.", NULL, NULL, NULL, arg);
/* No -- message earlier wil have covered it */
#endif
if (!(post_wanted || reply_wanted ||
spost_wanted || sreply_wanted)) {
ABORT_TARGET;
}
FREE(NewsHREF);
if (ProxyHREF) {
StrAllocCopy(NewsHost, ProxyHost);
FREE(ProxyHost);
FREE(ProxyHREF);
}
FREE(ListArg);
if (postfile) {
HTSYS_remove(postfile);
FREE(postfile);
}
return HT_NOT_LOADED;
}
/*
* This function clears all authorization information by
* invoking the free_NNTP_AuthInfo() function, which normally
* is invoked at exit. It allows a browser command to do
* this at any time, for example, if the user is leaving
* the terminal for a period of time, but does not want
* to end the current session. - FM
*/
void HTClearNNTPAuthInfo(void)
{
/*
* Need code to check cached documents and do something to ensure that any
* protected documents no longer can be accessed without a new retrieval.
* - FM
*/
/*
* Now free all of the authorization info. - FM
*/
free_NNTP_AuthInfo();
}
#ifdef USE_SSL
static int HTNewsGetCharacter(void)
{
if (!Handle)
return HTGetCharacter();
else
return HTGetSSLCharacter((void *) Handle);
}
int HTNewsProxyConnect(int sock,
const char *url,
HTParentAnchor *anAnchor,
HTFormat format_out,
HTStream *sink)
{
int status;
const char *arg = url;
char SSLprogress[256];
s = channel_s = sock;
Handle = HTGetSSLHandle();
SSL_set_fd(Handle, s);
HTSSLInitPRNG();
status = SSL_connect(Handle);
if (status <= 0) {
unsigned long SSLerror;
channel_s = -1;
CTRACE((tfp,
"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
url, status));
SSL_load_error_strings();
while ((SSLerror = ERR_get_error()) != 0) {
CTRACE((tfp, "HTNews: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
}
HTAlert("Unable to make secure connection to remote host.");
NEWS_NETCLOSE(s);
s = -1;
return HT_NOT_LOADED;
}
sprintf(SSLprogress, "Secure %d-bit %s (%s) NNTP connection",
SSL_get_cipher_bits(Handle, NULL),
SSL_get_cipher_version(Handle),
SSL_get_cipher(Handle));
_HTProgress(SSLprogress);
status = HTLoadNews(arg, anAnchor, format_out, sink);
channel_s = -1;
return status;
}
#endif /* USE_SSL */
#ifdef GLOBALDEF_IS_MACRO
#define _HTNEWS_C_1_INIT { "news", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNews, _HTNEWS_C_1_INIT);
#define _HTNEWS_C_2_INIT { "nntp", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNNTP, _HTNEWS_C_2_INIT);
#define _HTNEWS_C_3_INIT { "newspost", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNewsPost, _HTNEWS_C_3_INIT);
#define _HTNEWS_C_4_INIT { "newsreply", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNewsReply, _HTNEWS_C_4_INIT);
#define _HTNEWS_C_5_INIT { "snews", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNews, _HTNEWS_C_5_INIT);
#define _HTNEWS_C_6_INIT { "snewspost", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNewsPost, _HTNEWS_C_6_INIT);
#define _HTNEWS_C_7_INIT { "snewsreply", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNewsReply, _HTNEWS_C_7_INIT);
#else
GLOBALDEF HTProtocol HTNews =
{"news", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNNTP =
{"nntp", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNewsPost =
{"newspost", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNewsReply =
{"newsreply", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNews =
{"snews", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNewsPost =
{"snewspost", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNewsReply =
{"snewsreply", HTLoadNews, NULL};
#endif /* GLOBALDEF_IS_MACRO */
#endif /* not DISABLE_NEWS */