summaryrefslogtreecommitdiff
path: root/gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c')
-rw-r--r--gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c2824
1 files changed, 2824 insertions, 0 deletions
diff --git a/gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c b/gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c
new file mode 100644
index 00000000000..2094df68eec
--- /dev/null
+++ b/gnu/usr.bin/lynx/WWW/Library/Implementation/HTNews.c
@@ -0,0 +1,2824 @@
+/* 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 */
+#include "tcp.h"
+
+/* Implements:
+*/
+#include "HTNews.h"
+
+#include "HTCJK.h"
+#include "HTMIME.h"
+#include "HTTCP.h"
+
+#define FREE(x) if (x) {free(x); x = NULL;}
+
+/* this define should be in HTFont.h :( */
+#define HT_NON_BREAK_SPACE ((char)1) /* For now */
+
+#define NEWS_PORT 119 /* See rfc977 */
+#define SNEWS_PORT 563 /* See Lou Montulli */
+#define APPEND /* Use append methods */
+PUBLIC int HTNewsChunkSize = 30;/* Number of articles for quick display */
+PUBLIC int HTNewsMaxChunk = 40; /* Largest number of articles in one window */
+
+#ifndef DEFAULT_NEWS_HOST
+#define DEFAULT_NEWS_HOST "news"
+#endif /* DEFAULT_NEWS_HOST */
+#ifndef SERVER_FILE
+#define SERVER_FILE "/usr/local/lib/rn/server"
+#endif /* SERVER_FILE */
+
+#define NEWS_NETWRITE NETWRITE
+#define NEWS_NETCLOSE NETCLOSE
+#define NEXT_CHAR HTGetCharacter()
+
+#include <ctype.h>
+
+#include "HTML.h"
+#include "HTParse.h"
+#include "HTFormat.h"
+#include "HTAlert.h"
+
+#include "LYNews.h"
+#include "LYGlobalDefs.h"
+#include "LYLeaks.h"
+
+#define BIG 1024 /* @@@ */
+
+struct _HTStructured {
+ CONST HTStructuredClass * isa;
+ /* ... */
+};
+struct _HTStream
+{
+ HTStreamClass * isa;
+};
+
+#define LINE_LENGTH 512 /* Maximum length of line of ARTICLE etc */
+#define GROUP_NAME_LENGTH 256 /* Maximum length of group name */
+extern BOOLEAN scan_for_buried_news_references;
+extern BOOLEAN LYListNewsNumbers;
+extern BOOLEAN LYListNewsDates;
+extern HTCJKlang HTCJK;
+extern int interrupted_in_htgetcharacter;
+extern BOOL keep_mime_headers; /* Include mime headers and force raw text */
+extern BOOL using_proxy; /* Are we using an NNTP proxy? */
+
+/*
+** Module-wide variables.
+*/
+PUBLIC char * HTNewsHost = NULL; /* Default host */
+PRIVATE char * NewsHost = NULL; /* Current host */
+PRIVATE char * NewsHREF = NULL; /* Current HREF prefix */
+PRIVATE int s; /* Socket for NewsHost */
+PRIVATE int HTCanPost = FALSE; /* Current POST permission */
+PRIVATE char response_text[LINE_LENGTH+1]; /* Last response */
+/* PRIVATE HText * HT; */ /* the new hypertext */
+PRIVATE HTStructured * target; /* The output sink */
+PRIVATE HTStructuredClass targetClass; /* Copy of fn addresses */
+PRIVATE HTStream * rawtarget = NULL; /* The output sink for rawtext */
+PRIVATE HTStreamClass rawtargetClass; /* Copy of fn addresses */
+PRIVATE HTParentAnchor *node_anchor; /* Its anchor */
+PRIVATE int diagnostic; /* level: 0=none 2=source */
+PRIVATE BOOL rawtext = NO; /* Flag: HEAD or -mime_headers */
+PRIVATE HTList *NNTP_AuthInfo = NULL; /* AUTHINFO database */
+
+#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;
+
+PRIVATE void free_news_globals NOARGS
+{
+ if (s >= 0) {
+ NEWS_NETCLOSE(s);
+ s = -1;
+ }
+ FREE(HTNewsHost);
+ FREE(NewsHost);
+ FREE(NewsHREF);
+}
+
+PRIVATE void free_NNTP_AuthInfo NOARGS
+{
+ 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;
+}
+
+PUBLIC CONST char * HTGetNewsHost NOARGS
+{
+ return HTNewsHost;
+}
+
+PUBLIC void HTSetNewsHost ARGS1(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 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"
+*/
+PRIVATE BOOL initialized = NO;
+PRIVATE BOOL initialize NOARGS
+{
+#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 (getenv("NNTPSERVER")) {
+ StrAllocCopy(HTNewsHost, (char *)getenv("NNTPSERVER"));
+ if (TRACE) fprintf(stderr, "HTNews: NNTPSERVER defined as `%s'\n",
+ HTNewsHost);
+ } else {
+ char server_name[256];
+ FILE* fp = fopen(SERVER_FILE, "r");
+ if (fp) {
+ if (fscanf(fp, "%s", server_name)==1) {
+ StrAllocCopy(HTNewsHost, server_name);
+ if (TRACE) fprintf(stderr,
+ "HTNews: File %s defines news host as `%s'\n",
+ SERVER_FILE, HTNewsHost);
+ }
+ fclose(fp);
+ }
+ }
+ if (!HTNewsHost)
+ StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
+#endif /* NeXTStep */
+
+ s = -1; /* Disconnected */
+ atexit(free_news_globals);
+ 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.
+*/
+PRIVATE int response ARGS1(CONST char *,command)
+{
+ int result;
+ char * p = response_text;
+ char ch;
+
+ if (command) {
+ int status;
+ int length = strlen(command);
+ if (TRACE) fprintf(stderr, "NNTP command to be sent: %s", command);
+#ifdef NOT_ASCII
+ {
+ CONST char * p;
+ char * q;
+ char ascii[LINE_LENGTH+1];
+ for(p = command, q=ascii; *p; p++, q++) {
+ *q = TOASCII(*p);
+ }
+ status = NEWS_NETWRITE(s, ascii, length);
+ }
+#else
+ status = NEWS_NETWRITE(s, (char *)command, length);
+#endif /* NOT_ASCII */
+ if (status < 0){
+ if (TRACE) fprintf(stderr,
+ "HTNews: Unable to send command. Disconnecting.\n");
+ NEWS_NETCLOSE(s);
+ s = -1;
+ return status;
+ } /* if bad status */
+ } /* if command to be sent */
+
+ for (;;) {
+ if (((*p++ = NEXT_CHAR) == LF) ||
+ (p == &response_text[LINE_LENGTH])) {
+ *--p = '\0'; /* Terminate the string */
+ if (TRACE)
+ fprintf(stderr, "NNTP Response: %s\n", response_text);
+ sscanf(response_text, "%d", &result);
+ return result;
+ } /* if end of line */
+
+ if ((ch = *(p-1)) == (char)EOF) {
+ *(p-1) = '\0';
+ if (TRACE) {
+ if (interrupted_in_htgetcharacter) {
+ fprintf(stderr,
+ "HTNews: Interrupted on read, closing socket %d\n",
+ s);
+ } else {
+ fprintf(stderr,
+ "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 un upper case.
+** unknown may be in upper or lower or mixed case to match.
+*/
+PRIVATE BOOL match ARGS2 (CONST char *,unknown, CONST char *,template)
+{
+ CONST char * u = unknown;
+ CONST char * t = template;
+ 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
+*/
+PRIVATE NNTPAuthResult HTHandleAuthInfo ARGS1(
+ 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 an interactive user and a host. - FM
+ */
+ if (dump_output_immediately || !(host && *host))
+ return NNTPAUTH_ERROR;
+
+ /*
+ ** Check for an existing authorization entry. - FM
+ */
+ if (NNTP_AuthInfo != NULL) {
+ cur = NNTP_AuthInfo;
+ while (NULL != (auth = (NNTPAuth *)HTList_nextObject(cur))) {
+ if (!strcmp(auth->host, host)) {
+ UserName = auth->user;
+ PassWord = auth->pass;
+ break;
+ }
+ }
+ } else {
+ NNTP_AuthInfo = HTList_new();
+ atexit(free_NNTP_AuthInfo);
+ }
+
+ /*
+ ** Handle the username. - FM
+ */
+ buffer[511] = '\0';
+ tries = 3;
+
+ while (tries) {
+ if (UserName == NULL) {
+ if ((msg = (char *)calloc(1, (strlen(host) + 30))) == NULL) {
+ outofmem(__FILE__, "HTHandleAuthInfo");
+ }
+ sprintf(msg, "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", 495, UserName, CR, LF);
+ if ((status = response(buffer)) < 0) {
+ if (status == HT_INTERRUPTED)
+ _HTProgress("Connection interrupted.");
+ else
+ HTAlert("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 =
+ (NNTPAuth *)calloc(1, sizeof(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("Change username?")) {
+ if (!auth || auth->user != UserName) {
+ FREE(UserName);
+ }
+ if ((UserName = HTPrompt("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) {
+ if ((msg = (char *)calloc(1, (strlen(host) + 30))) == NULL) {
+ outofmem(__FILE__, "HTHandleAuthInfo");
+ }
+ sprintf(msg, "Password for news host '%s':", host);
+ PassWord = HTPromptPassword(msg);
+ FREE(msg);
+ if (!(PassWord && *PassWord)) {
+ FREE(PassWord);
+ return NNTPAUTH_ERROR;
+ }
+ }
+ sprintf(buffer, "AUTHINFO PASS %.*s%c%c", 495, PassWord, CR, LF);
+ if ((status = response(buffer)) < 0) {
+ if (status == HT_INTERRUPTED) {
+ _HTProgress("Connection interrupted.");
+ } else {
+ HTAlert("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 =
+ (NNTPAuth *)calloc(1, sizeof(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("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 ) "
+*/
+PRIVATE char * author_name ARGS1 (char *,email)
+{
+ static char *name = NULL;
+ char *p, *e;
+
+ StrAllocCopy(name, email);
+ if (TRACE)
+ fprintf(stderr,"Trying to find name in: %s\n",name);
+
+ if ((p = strchr(name, '(')) && (e = strchr(name, ')'))) {
+ if (e > p) {
+ *e = '\0'; /* Chop off everything after the ')' */
+ return HTStrip(p+1); /* Remove leading and trailing spaces */
+ }
+ }
+
+ if ((p = strchr(name, '<')) && (e = strchr(name, '>'))) {
+ if (e > p) {
+ strcpy(p, e+1); /* 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 ) "
+*/
+PRIVATE char * author_address ARGS1(char *,email)
+{
+ static char *address = NULL;
+ char *p, *at, *e;
+
+ StrAllocCopy(address, email);
+ if (TRACE)
+ fprintf(stderr,"Trying to find address in: %s\n",address);
+
+ if ((p = strchr(address, '<'))) {
+ if ((e = strchr(p, '>')) && (at = strchr(p, '@'))) {
+ if (at < e) {
+ *e = '\0'; /* Remove > */
+ return HTStrip(p+1); /* Remove leading and trailing spaces */
+ }
+ }
+ }
+
+ if ((p = strchr(address, '(')) &&
+ (e = strchr(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 = strchr(address, '@')) && at > address) {
+ p = (at - 1);
+ e = (at + 1);
+ while (p > address && !isspace((unsigned char)*p))
+ p--;
+ while (*e && !isspace((unsigned char)*e))
+ e++;
+ *e = 0;
+ return HTStrip(p);
+ }
+
+ /*
+ ** Default to the first word.
+ */
+ p = address;
+ while (isspace((unsigned char)*p))
+ p++; /* find first non-space */
+ e = p;
+ while (!isspace((unsigned char)*e) && *e != '\0')
+ e++; /* find next space or end */
+ *e = '\0'; /* terminate space */
+
+ return(p);
+}
+
+/* Start anchor element
+** --------------------
+*/
+PRIVATE void start_anchor ARGS1(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] = (i == HTML_A_HREF);
+ }
+ ((CONST char **)value)[HTML_A_HREF] = href;
+ (*targetClass.start_element)(target, HTML_A , present,
+ (CONST char **)value, -1, 0);
+}
+
+/* Start link element
+** ------------------
+*/
+PRIVATE void start_link ARGS2(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] = (i == HTML_LINK_HREF || i == HTML_LINK_REV);
+ }
+ ((CONST char **)value)[HTML_LINK_HREF] = href;
+ ((CONST char **)value)[HTML_LINK_REV] = rev;
+ (*targetClass.start_element)(target, HTML_LINK, present,
+ (CONST char **)value, -1, 0);
+}
+
+/* Start list element
+** ------------------
+*/
+PRIVATE void start_list ARGS1(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] = (i == HTML_OL_SEQNUM || i == HTML_OL_START);
+ sprintf(SeqNum, "%d", seqnum);
+ ((CONST char **)value)[HTML_OL_SEQNUM] = SeqNum;
+ ((CONST char **)value)[HTML_OL_START] = SeqNum;
+ (*targetClass.start_element)(target, HTML_OL , present,
+ (CONST char **)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 '>'
+*/
+PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
+{
+ char href[LINE_LENGTH+1];
+
+ {
+ CONST char * p;
+ strcpy(href, NewsHREF);
+ for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p!=','); p++)
+ ;
+ strncat(href, addr, p-addr); /* Make complete hypertext reference */
+ }
+
+ start_anchor(href);
+ PUTS(text);
+ END(HTML_A);
+}
+
+/* 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.
+*/
+PRIVATE void write_anchors ARGS1 (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
+** --------------------
+*/
+PRIVATE void abort_socket NOARGS
+{
+ if (TRACE)
+ fprintf(stderr,
+ "HTNews: EOF on read, closing socket %d\n", s);
+ NEWS_NETCLOSE(s); /* End of file, close socket */
+ 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
+** -------------------------------------------
+*/
+PRIVATE BOOLEAN valid_header ARGS1(
+ 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(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.
+*/
+PRIVATE void post_article ARGS1(
+ 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((postfile ? postfile : ""), "r")) == NULL) {
+ HTAlert("Cannot open temporary file for news 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, sizeof(line), 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 = 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 < 475) {
+ strcat(buf, "From: anonymous@nowhere.you.know");
+ strcat(buf, crlf);
+ blen += 34;
+ } else {
+ NEWS_NETWRITE(s, buf, blen);
+ sprintf(buf,
+ "From: anonymous@nowhere.you.know%s", 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) < 511) {
+ strcat(buf, line);
+ blen += llen;
+ } else {
+ NEWS_NETWRITE(s, buf, blen);
+ strcpy(buf, line);
+ blen = llen;
+ }
+ }
+ fclose(fd);
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+
+ /*
+ ** Send the nntp EOF and get the server's response. - FM
+ */
+ if (blen < 508) {
+ strcat(buf, ".");
+ strcat(buf, crlf);
+ blen += 3;
+ NEWS_NETWRITE(s, buf, blen);
+ } else {
+ NEWS_NETWRITE(s, buf, blen);
+ sprintf(buf, ".%s", crlf);
+ blen = 3;
+ 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);
+ }
+}
+
+/* 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
+*/
+PRIVATE int read_article NOARGS
+{
+ 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;
+ 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) {
+ char ch = *p++ = NEXT_CHAR;
+ if (ch == (char)EOF) {
+ if (interrupted_in_htgetcharacter) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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 ((ch == LF) || (p == &line[LINE_LENGTH])) {
+ *--p = '\0'; /* Terminate the string */
+ if (TRACE)
+ fprintf(stderr, "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 ((unsigned char)full_line[1] < ' ') {
+ done = YES;
+ break;
+ }
+ } else if ((unsigned char)full_line[0] < ' ') {
+ break; /* End of Header? */
+
+ } else if (match(full_line, "SUBJECT:")) {
+ StrAllocCopy(subject, HTStrip(strchr(full_line,':')+1));
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(subject, subject);
+ HTrjis(subject, 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));
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(organization, organization);
+ HTrjis(organization, organization);
+ }
+
+ } else if (match(full_line, "FROM:")) {
+ StrAllocCopy(from, HTStrip(strchr(full_line,':')+1));
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(from, from);
+ HTrjis(from, from);
+ }
+
+ } else if (match(full_line, "REPLY-TO:")) {
+ StrAllocCopy(replyto, HTStrip(strchr(full_line,':')+1));
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(replyto, replyto);
+ HTrjis(replyto, 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));
+
+ } /* 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, replyto ? replyto : from);
+ StrAllocCopy(href,"mailto:");
+ StrAllocCat(href, author_address(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(from);
+ 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);
+ }
+
+ 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, "snews:", 6))
+ StrAllocCopy(href,"snewsreply://");
+ else
+ StrAllocCopy(href,"newsreply://");
+ StrAllocCat(href, NewsHost);
+ StrAllocCat(href, "/");
+ StrAllocCat(href, (followupto ? followupto : newsgroups));
+
+ 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');
+
+ START(HTML_DT);
+ START(HTML_B);
+ PUTS("Followup to:");
+ END(HTML_B);
+ PUTC(' ');
+ start_anchor(href);
+ PUTS("newsgroup(s)");
+ 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 - kw
+ */
+ ;
+ } else if (diagnostic) {
+ /*
+ ** Read in the HEAD and BODY of the Article
+ ** as XMP formatted text. - FM
+ */
+ START(HTML_XMP);
+ } else {
+ /*
+ ** Read in the BODY of the Article
+ ** as PRE formatted text. - FM
+ */
+ START(HTML_PRE);
+ }
+ PUTC('\n');
+
+ p = line;
+ while (!done) {
+ char ch = *p++ = NEXT_CHAR;
+ if (ch == (char)EOF) {
+ if (interrupted_in_htgetcharacter) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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 ((ch == LF) || (p == &line[LINE_LENGTH])) {
+ *p++ = '\0'; /* Terminate the string */
+ if (TRACE)
+ fprintf(stderr, "B %s", line);
+ if (line[0] == '.') {
+ /*
+ ** End of article?
+ */
+ if ((unsigned char)line[1] < ' ') {
+ done = YES;
+ 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 = strchr(p2,'>');
+ char *at = strchr(p2, '@');
+ if (q && at && at<q) {
+ char c = q[1];
+ q[1] = 0; /* chop up */
+ p2 += 7;
+ *p2 = 0;
+ while (*l) {
+ if (strncmp(l, "news:", 5) &&
+ 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, "mailto:", 7) &&
+ 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, "news:", 5) &&
+ 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, "mailto:", 7) &&
+ 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.
+*/
+PRIVATE int read_list ARGS1(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 = 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) {
+ char ch = NEXT_CHAR;
+ if (ch == (char)EOF) {
+ if (interrupted_in_htgetcharacter) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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]) {
+ if (TRACE) {
+ fprintf(stderr, "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 */
+ if (TRACE)
+ fprintf(stderr,
+ "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 */
+ if (TRACE)
+ fprintf(stderr, "B %s", line);
+ if (line[0] == '.') {
+ /*
+ ** End of article?
+ */
+ if ((unsigned char)line[1] < ' ') {
+ done = YES;
+ 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) {
+ START(HTML_DT);
+ sprintf(line, "No matches for: %s", arg);
+ PUTS(line);
+ MAYBE_END(HTML_DT);
+ }
+ 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.
+**
+*/
+PRIVATE int read_group ARGS3(
+ CONST char *,groupName,
+ int,first_required,
+ int,last_required)
+{
+ char line[LINE_LENGTH+1];
+ char author[LINE_LENGTH+1];
+ char subject[LINE_LENGTH+1];
+ char *date = NULL;
+ int i;
+ char *p;
+ BOOL done;
+
+ char buffer[LINE_LENGTH];
+ char *reference = NULL; /* Href for article */
+ int art; /* Article number WITHIN GROUP */
+ int status, count, first, last; /* Response fields */
+ /* count is only an upper limit */
+
+ author[0] = '\0';
+ 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);
+ if (TRACE)
+ fprintf(stderr,
+ "Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
+ status, count, first, last, first_required, last_required);
+ if (last == 0) {
+ PUTS("\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("\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;
+ }
+ if (TRACE)
+ fprintf(stderr, " Chunk will be (%d-%d)\n",
+ first_required, last_required);
+
+ /*
+ ** Set window title.
+ */
+ sprintf(buffer, "%s, Articles %d-%d",
+ groupName, first_required, last_required);
+ START(HTML_H1);
+ PUTS(buffer);
+ 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;
+ sprintf(buffer, "%s%s/%d-%d", NewsHREF, groupName,
+ before, first_required-1);
+ if (TRACE)
+ fprintf(stderr, " Block before is %s\n", buffer);
+ PUTC('(');
+ start_anchor(buffer);
+ PUTS("Earlier articles");
+ END(HTML_A);
+ PUTS("...)\n");
+ START(HTML_P);
+ PUTC('\n');
+ }
+
+ done = NO;
+
+/*#define USE_XHDR*/
+#ifdef USE_XHDR
+ if (count > FAST_THRESHOLD) {
+ sprintf(buffer,
+ "\nThere are about %d articles currently available in %s, IDs as follows:\n\n",
+ count, groupName);
+ PUTS(buffer);
+ sprintf(buffer, "XHDR Message-ID %d-%d%c%c", first, last, CR, LF);
+ status = response(buffer);
+ if (status == 221) {
+ p = line;
+ while (!done) {
+ char ch = *p++ = NEXT_CHAR;
+ if (ch == (char)EOF) {
+ if (interrupted_in_htgetcharacter) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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 ((ch == '\n') || (p == &line[LINE_LENGTH])) {
+ *p = '\0'; /* Terminate the string */
+ if (TRACE)
+ fprintf(stderr, "X %s", line);
+ if (line[0] == '.') {
+ /*
+ ** End of article?
+ */
+ if ((unsigned char)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("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 ) {
+ char ch = *p++ = NEXT_CHAR;
+ if (ch == (char)EOF) {
+ if (interrupted_in_htgetcharacter) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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 ((ch == LF) ||
+ (p == &line[LINE_LENGTH])) {
+
+ *--p = '\0'; /* Terminate & chop LF*/
+ p = line; /* Restart at beginning */
+ if (TRACE)
+ fprintf(stderr, "G %s\n", line);
+ switch(line[0]) {
+
+ case '.':
+ /*
+ ** End of article?
+ */
+ done = ((unsigned char)line[1] < ' ');
+ break;
+
+ case 'S':
+ case 's':
+ if (match(line, "SUBJECT:")) {
+ strcpy(subject, line+9);/* Save subject */
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(subject, subject);
+ HTrjis(subject, 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;
+ strcpy(author,
+ author_name(strchr(line,':')+1));
+ if (HTCJK == JAPANESE) {
+ HTmmdecode(author, author);
+ HTrjis(author, 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);
+ sprintf(buffer, "\"%s\"", subject);
+ if (reference) {
+ write_anchor(buffer, reference);
+ FREE(reference);
+ } else {
+ PUTS(buffer);
+ }
+ if (author[0] != '\0') {
+ PUTS(" - ");
+ if (LYListNewsDates)
+ START(HTML_I);
+ PUTS(author);
+ if (LYListNewsDates)
+ END(HTML_I);
+ author[0] = '\0';
+ }
+ if (date) {
+ if (!diagnostic) {
+ for (i = 0; date[i]; i++) {
+ if (date[i] == ' ') {
+ date[i] = HT_NON_BREAK_SPACE;
+ }
+ }
+ }
+ sprintf(buffer, " [%s]", date);
+ PUTS(buffer);
+ FREE(date);
+ }
+ MAYBE_END(HTML_LI);
+ /*
+ ** Indicate progress! @@@@@@
+ */
+ } else if (status == HT_INTERRUPTED) {
+ interrupted_in_htgetcharacter = 0;
+ if (TRACE)
+ fprintf(stderr,
+ "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)
+ strcpy(buffer, "Status:");
+ 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 */
+ } /* 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)
+ sprintf(buffer, "%s%s", NewsHREF, groupName); /* original group */
+ else
+ sprintf(buffer, "%s%s/%d-%d", NewsHREF, groupName,
+ last_required+1, after);
+ if (TRACE)
+ fprintf(stderr, " Block after is %s\n", buffer);
+ PUTC('(');
+ start_anchor(buffer);
+ PUTS("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, "snews:", 6))
+ StrAllocCopy(href,"snewspost://");
+ else
+ StrAllocCopy(href,"newspost://");
+ StrAllocCat(href, NewsHost);
+ StrAllocCat(href, "/");
+ StrAllocCat(href,groupName);
+ start_anchor(href);
+ PUTS("Post to ");
+ PUTS(groupName);
+ END(HTML_A);
+ FREE(href);
+ } else {
+ START(HTML_HR);
+ }
+ PUTC('\n');
+ return(HT_LOADED);
+}
+
+/* Load by name. HTLoadNews
+** =============
+*/
+PRIVATE int HTLoadNews ARGS4(
+ CONST char *, arg,
+ HTParentAnchor *, anAnchor,
+ HTFormat, format_out,
+ HTStream*, stream)
+{
+ char command[260]; /* 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 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;
+
+ diagnostic = (format_out == WWW_SOURCE || /* set global flag */
+ format_out == HTAtom_for("www/download") ||
+ format_out == HTAtom_for("www/dump"));
+ rawtext = NO;
+
+ if (TRACE) fprintf(stderr, "HTNews: Looking for %s\n", arg);
+
+ if (!initialized)
+ initialized = initialize();
+ if (!initialized)
+ return -1; /* FAIL */
+
+ FREE(NewsHREF);
+ command[0] = '\0';
+ command[259] = '\0';
+ proxycmd[0] = '\0';
+ proxycmd[259] = '\0';
+
+ {
+ CONST char * p1 = arg;
+
+ /*
+ ** 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
+ */
+ spost_wanted = (strstr(arg, "snewspost:") != NULL);
+ sreply_wanted = (!(spost_wanted) &&
+ strstr(arg, "snewsreply:") != NULL);
+ post_wanted = (!(spost_wanted || sreply_wanted) &&
+ strstr(arg, "newspost:") != NULL);
+ reply_wanted = (!(spost_wanted || sreply_wanted ||
+ post_wanted) &&
+ strstr(arg, "newsreply:") != NULL);
+ group_wanted = (!(spost_wanted || sreply_wanted ||
+ post_wanted || reply_wanted) &&
+ strchr(arg, '@') == NULL) && (strchr(arg, '*') == NULL);
+ list_wanted = (!(spost_wanted || sreply_wanted ||
+ post_wanted || reply_wanted ||
+ group_wanted) &&
+ strchr(arg, '@') == NULL) && (strchr(arg, '*') != NULL);
+
+ if (!strncasecomp(arg, "snewspost:", 10) ||
+ !strncasecomp(arg, "snewsreply:", 11)) {
+ HTAlert(
+ "This client does not contain support for posting to news with SSL.");
+ return HT_NOT_LOADED;
+ }
+ 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("Invalid URL!");
+ 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);
+ sprintf(command, "%s://%.245s/",
+ (post_wanted ?
+ "newspost" :
+ (reply_wanted ?
+ "newreply" :
+ (spost_wanted ?
+ "snewspost" : "snewsreply"))), NewsHost);
+ StrAllocCopy(NewsHREF, command);
+
+ /*
+ ** 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 (!strncasecomp (arg, "nntp:", 5)) {
+ 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);
+ sprintf(command, "nntp://%.251s/", NewsHost);
+ StrAllocCopy(NewsHREF, command);
+ }
+ else if (!strncasecomp(arg, "snews:", 6)) {
+ HTAlert("This client does not contain support for SNEWS URLs.");
+ return HT_NOT_LOADED;
+ }
+ 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);
+ sprintf(command, "news://%.251s/", 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, "news:");
+ }
+
+ /*
+ ** 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, "snews:", 6) ||
+ !strncasecomp(p1, "snewspost:", 10) ||
+ !strncasecomp(p1, "snewsreply:", 11)) {
+ StrAllocCopy(ProxyHost, NewsHost);
+ if ((cp = HTParse(p1, "", PARSE_HOST)) != NULL && *cp != '\0') {
+ sprintf(command, "snews://%.250s", cp);
+ StrAllocCopy(NewsHost, cp);
+ } else {
+ sprintf(command, "snews://%.250s", NewsHost);
+ }
+ command[258] = '\0';
+ FREE(cp);
+ sprintf(proxycmd, "GET %.251s%c%c%c%c", command, CR, LF, CR, LF);
+ if (TRACE)
+ fprintf(stderr,
+ "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 newgroup
+ ** (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) {
+ sprintf(command, "XGTITLE %.*s", 249, p1);
+ } else if (group_wanted) {
+ char * slash = strchr(p1, '/');
+ strcpy(command, "GROUP ");
+ first = 0;
+ last = 0;
+ if (slash) {
+ *slash = '\0';
+ strcpy(groupName, p1);
+ *slash = '/';
+ (void)sscanf(slash+1, "%d-%d", &first, &last);
+ if ((first > 0) && (isdigit(*(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 {
+ strcpy(groupName, p1);
+ }
+ strcat(command, groupName);
+ } else {
+ strcpy(command, "ARTICLE ");
+ if (strchr(p1, '<') == 0)
+ strcat(command,"<");
+ strcat(command, p1);
+ if (strchr(p1, '>') == 0)
+ strcat(command,">");
+ }
+
+ {
+ 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 = (head_wanted || keep_mime_headers);
+ }
+ if (rawtext) {
+ node_anchor = anAnchor;
+ rawtarget = HTStreamStack(WWW_PLAINTEXT,
+ format_out,
+ stream, anAnchor);
+ if (!rawtarget) {
+ FREE(NewsHost);
+ FREE(NewsHREF);
+ FREE(ProxyHost);
+ FREE(ProxyHREF);
+ FREE(ListArg);
+ HTAlert("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)) {
+ node_anchor = anAnchor;
+ 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, "news:")) {
+ sprintf (url, "lose://%.251s/", NewsHost);
+ } else if (ProxyHREF) {
+ sprintf (url, "%.259s", ProxyHREF);
+ } else {
+ sprintf (url, "%.259s", NewsHREF);
+ }
+ if (TRACE)
+ fprintf (stderr, "News: doing HTDoConnect on '%s'\n", url);
+
+ _HTProgress("Connecting to NewsHost ...");
+
+ status = HTDoConnect (url, "NNTP", NEWS_PORT, &s);
+ if (status == HT_INTERRUPTED) {
+ /*
+ ** Interrupt cleanly.
+ */
+ if (TRACE)
+ fprintf(stderr,
+ "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);
+ if (postfile) {
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+ FREE(postfile);
+ }
+ return HT_NOT_LOADED;
+ }
+ if (status < 0) {
+ char message[256];
+ NEWS_NETCLOSE(s);
+ s = -1;
+ if (TRACE)
+ fprintf(stderr,
+ "HTNews: Unable to connect to news host.\n");
+ if (retries < 1)
+ continue;
+ sprintf(message, "Could not access %s.", NewsHost);
+ 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 HTLoadError(stream, 500, message);
+ } else {
+ if (TRACE)
+ fprintf(stderr, "HTNews: Connected to news host %s.\n",
+ NewsHost);
+ HTInitInput(s); /* set up buffering */
+ if (proxycmd[0]) {
+ status = NEWS_NETWRITE(s, proxycmd, strlen(proxycmd));
+ if (TRACE)
+ fprintf(stderr,
+ "HTNews: Proxy command returned status '%d'.\n",
+ status);
+ }
+ if (((status = response(NULL)) / 100) != 2) {
+ char message[BIG];
+ 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) {
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+ FREE(postfile);
+ }
+ return(HT_NOT_LOADED);
+ }
+ if (retries < 1)
+ continue;
+ sprintf(message,
+ "Can't read news info. News host %.20s responded: %.200s",
+ NewsHost, response_text);
+ return HTLoadError(stream, 500, message);
+ }
+ if (status == 200) {
+ HTCanPost = TRUE;
+ } else {
+ HTCanPost = FALSE;
+ if (post_wanted || reply_wanted ||
+ spost_wanted || sreply_wanted) {
+ HTAlert("Cannot POST to this host.");
+ FREE(NewsHREF);
+ if (ProxyHREF) {
+ StrAllocCopy(NewsHost, ProxyHost);
+ 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);
+ }
+ }
+ }
+ } /* If needed opening */
+
+ if (post_wanted || reply_wanted ||
+ spost_wanted || sreply_wanted) {
+ if (!HTCanPost) {
+ HTAlert("Cannot POST to this host.");
+ FREE(NewsHREF);
+ if (ProxyHREF) {
+ StrAllocCopy(NewsHost, ProxyHost);
+ 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);
+ }
+ 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 ((status = response(buffer)) == HT_INTERRUPTED) {
+ _HTProgress("Connection interrupted.");
+ break;
+ }
+ }
+ }
+
+Send_NNTP_command:
+ 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 rorresponds 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 to this host.");
+ if (postfile) {
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+ }
+ } else {
+ post_article(postfile);
+ }
+ FREE(postfile);
+ status = HT_NOT_LOADED;
+ } else if (list_wanted) {
+ /*
+ ** List available newsgroups. - FM
+ */
+ _HTProgress("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("Reading list of articles in newsgroup.");
+ status = read_group(groupName, first, last);
+ } else {
+ /*
+ ** Get an article from a news group. - FM
+ */
+ _HTProgress("Reading news article.");
+ status = read_article();
+ }
+ 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) {
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+ FREE(postfile);
+ }
+ return status;
+ } /* Retry loop */
+
+ /* HTAlert("Sorry, could not load requested news."); */
+
+/* NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
+ NULL,NULL,NULL, arg);No -- message earlier wil have covered it */
+
+ 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) {
+#ifdef VMS
+ while (remove(postfile) == 0)
+ ; /* loop through all versions */
+#else
+ remove(postfile);
+#endif /* VMS */
+ FREE(postfile);
+ }
+ return HT_NOT_LOADED;
+}
+
+/*
+** This function clears all authorization information by
+** invoking the free_HTAAGlobals() 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
+*/
+PUBLIC void HTClearNNTPAuthInfo NOARGS
+{
+ /*
+ ** 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 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 PUBLIC HTProtocol HTNews = { "news", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTNNTP = { "nntp", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTNewsPost = { "newspost", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTNewsReply = { "newsreply", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTSNews = { "snews", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTSNewsPost = { "snewspost", HTLoadNews, NULL };
+GLOBALDEF PUBLIC HTProtocol HTSNewsReply = { "snewsreply", HTLoadNews, NULL };
+#endif /* GLOBALDEF_IS_MACRO */