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