/* 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 /* DEFAULE_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 #include "HTML.h" #include "HTParse.h" #include "HTFormat.h" #include "HTAlert.h" #include "LYLeaks.h" #define BIG 1024 /* @@@ */ 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 */ extern BOOLEAN scan_for_buried_news_references; extern BOOLEAN LYListNewsNumbers; extern BOOLEAN LYListNewsDates; extern HTCJKlang HTCJK; extern int interrupted_in_htgetcharacter; 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 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 HTParentAnchor *node_anchor; /* Its anchor */ PRIVATE int diagnostic; /* level: 0=none 2=source */ #define PUTC(c) (*targetClass.put_character)(target, c) #define PUTS(s) (*targetClass.put_string)(target, s) #define START(e) (*targetClass.start_element)(target, e, 0, 0, 0) #define END(e) (*targetClass.end_element)(target, e, 0) PRIVATE void free_news_globals NOARGS { FREE(HTNewsHost); FREE(NewsHost); FREE(NewsHREF); } 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 { char *cp = NULL; /* ** 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 */ } /* 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 " ** or " tim@online.cern.ch ( Tim Berners-Lee ) " */ PRIVATE char * author_name ARGS1 (char *,email) { static char *name = NULL; char *s, *e; StrAllocCopy(name, email); if (TRACE) fprintf(stderr,"Trying to find name in: %s\n",name); if ((s = strchr(name, '(')) && (e = strchr(name, ')'))) { if (e > s) { *e = '\0'; /* Chop off everything after the ')' */ return HTStrip(s+1); /* Remove leading and trailing spaces */ } } if ((s = strchr(name, '<')) && (e = strchr(name, '>'))) { if (e > s) { strcpy(s, 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 " ** or " montulli@spaced.out.galaxy.net ( Lou "The Stud" Montulli ) " */ PRIVATE char * author_address ARGS1(char *,email) { static char *address = NULL; char *s, *at, *e; StrAllocCopy(address, email); if (TRACE) fprintf(stderr,"Trying to find address in: %s\n",address); if ((s = strchr(address, '<'))) { if ((e = strchr(s, '>')) && (at = strchr(s, '@'))) { if (at < e) { *e = '\0'; /* Remove > */ return HTStrip(s+1); /* Remove leading and trailing spaces */ } } } if ((s = strchr(address, '(')) && (e = strchr(address, ')')) && (at = strchr(address, '@'))) { if (e > s && at < e) { *s = '\0'; /* Chop off everything after the ')' */ return HTStrip(address); /* Remove leading and trailing spaces */ } } if ((at = strchr(address, '@')) && at > address) { s = (at - 1); e = (at + 1); while (s > address && !isspace((unsigned char)*s)) s--; while (*e && !isspace((unsigned char)*e)) e++; *e = 0; return HTStrip(s); } /* ** Default to the first word. */ s = address; while (isspace((unsigned char)*s)) s++; /* find first non-space */ e = s; while (!isspace((unsigned char)*e) && *e != '\0') e++; /* find next space or end */ *e = '\0'; /* terminate space */ return(s); } /* 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, 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, 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, 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) { 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] == '.') { if (full_line[1] < ' ') { /* End of article? */ done = YES; break; } } else if (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); 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); PUTC('\n'); FREE(from); FREE(replyto); } if (date) { START(HTML_DT); START(HTML_B); PUTS("Date:"); END(HTML_B); PUTC(' '); PUTS(date); PUTC('\n'); FREE(date); } if (organization) { START(HTML_DT); START(HTML_B); PUTS("Organization:"); END(HTML_B); PUTC(' '); PUTS(organization); 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'); START(HTML_DD); write_anchors(newsgroups); PUTC('\n'); START(HTML_DT); START(HTML_B); PUTS("Followup to:"); END(HTML_B); PUTC(' '); start_anchor(href); PUTS("newsgroup(s)"); END(HTML_A); PUTC('\n'); } FREE(newsgroups); FREE(followupto); if (references) { START(HTML_DT); START(HTML_B); PUTS("References:"); END(HTML_B); PUTC('\n'); START(HTML_DD); write_anchors(references); PUTC('\n'); FREE(references); } END(HTML_DLC); PUTC('\n'); FREE(href); } 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] == '.') { if (line[1] < ' ') { /* End of article? */ done = YES; break; } else { /* Line starts with dot */ PUTS(&line[1]); /* Ignore first dot */ } } 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 *p; while (p=strstr(l, "rticle <")) { char *q = strchr(p,'>'); char *at = strchr(p, '@'); if (q && at && at)\"")); while (*l && !strchr(" \r\n\t,>)\"", *l)) PUTC(*l++); END(HTML_A); FREE(href); } } *p = '<'; /* again */ *q = 0; start_anchor(p+1); *q = '>'; /* again */ PUTS(p); 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 (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; 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 = *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 */ FREE(pattern); 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] == '.') { if (line[1] < ' ') { /* End of article? */ done = YES; break; } else { /* Line starts with dot */ START(HTML_DT); PUTS(&line[1]); } } 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++; PUTC('\n'); START(HTML_DD); PUTS(&line[i+1]); /* put description */ } 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); 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); } 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] == '.') { if (line[1] < ' ') { /* End of article? */ 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 '.': done = (line[1] < ' '); /* End of article? */ break; case 'S': case 's': if (match(line, "SUBJECT:")) strcpy(subject, line+9);/* Save 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 * p; strcpy(author, author_name(strchr(line,':')+1)); p = author + strlen(author) - 1; if (*p==LF) *p = '\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); } /* ** 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); } /* 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 ** ============= */ PUBLIC 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 */ int first, last; /* First and last articles asked for */ char *cp; 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")); 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 ** 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", (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) { strcpy(command, "LIST NEWSGROUPS"); } 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 */ } /* ** 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)) (*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; } 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)) (*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); } 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) { extern char *LYNewsPost PARAMS((char *newsgroups, BOOLEAN followup)); 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_INERRUPTED, because if ** the reader mode command is not needed, the server ** probably return 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; } } Send_NNTP_command: if ((status = response(command)) == HT_INTERRUPTED) { _HTProgress("Connection interrupted."); break; } if (status < 0) { if (retries < 1) { continue; } else { break; } } if ((status/100) != 2 && status != 340) { if (retries) 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 (post_wanted || reply_wanted || spost_wanted || sreply_wanted) { 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) { _HTProgress("Reading list of available newsgroups."); status = read_list(ListArg); } else if (group_wanted) { 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, "ARTICLE %d%c%c", 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 { _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)) (*targetClass._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)) (*targetClass._abort)(target, NULL); 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; } #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 */