3147 lines
79 KiB
C
3147 lines
79 KiB
C
/*
|
|
* $LynxId: HTNews.c,v 1.81 2022/04/01 00:18:22 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) = '\0';
|
|
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 == WWW_DOWNLOAD ||
|
|
format_out == 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 ? (cp + 1) : (arg + 6));
|
|
}
|
|
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 ? (cp + 1) : (arg + 7));
|
|
}
|
|
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 ? (cp + 1) : (arg + 6));
|
|
}
|
|
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 {
|
|
char *cp2;
|
|
|
|
/*
|
|
* Reset p1 so that it points to the newsgroup (or a wildcard),
|
|
* or the article.
|
|
*/
|
|
if (!(cp2 = strrchr((p1 + 6), '/')) || *(cp2 + 1) == '\0') {
|
|
p1 = "*";
|
|
group_wanted = FALSE;
|
|
list_wanted = TRUE;
|
|
} else {
|
|
p1 = (cp2 + 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 will 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 */
|