ftp.nice.ch/pub/next/connectivity/www/WorldWideWeb.0.16.N.bs.tar.gz#/WWW/NextStep/src_0.16/NewsAccess.m

This is NewsAccess.m in view mode; [Download] [Up]

//								NewsAccess.m

// A HyperAccess object provides access to hyperinformation, using particular
//	protocols and data format transformations. This one provides access to
//	the Internet/Usenet News system using NNTP/TCP().

// History:
//	26 Sep 90	Written TBL

#define NEWS_PORT 119		/* See rfc977 */
#define APPEND			/* Use append methods */
#define MAX_CHUNK	40	/* Largest number of articles in one window */
#define CHUNK_SIZE	20	/* Optimum number of articles for quick display */

#import "NewsAccess.h"
#import <defaults/defaults.h>
#import "Anchor.h"
#import "HTParse.h"
#import "HTStyle.h"
#import <ctype.h>

extern HTStyleSheet * styleSheet;

#define NEXT_CHAR next_char()
#define LINE_LENGTH 512			/* Maximum length of line of ARTICLE etc */
#define GROUP_NAME_LENGTH	256	/* Maximum length of group name */
/*	Module parameters:
**	-----------------
**
**  These may be undefined and redefined by syspec.h
*/

#define NETCLOSE close	    /* Routine to close a TCP-IP socket		*/
#define NETREAD  read	    /* Routine to read from a TCP-IP socket	*/
#define NETWRITE write	    /* Routine to write to a TCP-IP socket	*/

#ifdef NeXT
#import <libc.h>		/* NeXT has all this packaged up */
#define ntohs(x) (x)
#define htons(x) (x)
#else
#include <string.h>		/* For bzero etc */
#include <stdio.h>

/*	TCP-specific types
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>	    /* independent */
#include <sys/time.h>	    /* independent */
extern char *malloc();
extern void free();
extern char *strncpy();
#endif

#include <netinet/in.h>
#include <arpa/inet.h>	    /* Must be after netinet/in.h */
#include <netdb.h>
#import <streams/streams.h>
#import "HTUtils.h"		/* Coding convention macros */

@implementation NewsAccess

//	Module-wide variables

static const char * NewsHost;
static struct sockaddr_in soc_address;		/* Binary network address */
static int s;					/* Socket for conn. to NewsHost */
static char response_text[LINE_LENGTH+1];	/* Last response from NewsHost */
static HyperText *	HT;			/* the new hypertext  		*/
static int	diagnostic;			/* level: 0=none 1=rtf 2=source */

static HTStyle *addressStyle;			/* For heading, from address etc */
static HTStyle *textStyle;			/* Text style */

#define INPUT_BUFFER_SIZE 4096
static char input_buffer[INPUT_BUFFER_SIZE];		/* Input buffer */
static char * input_read_pointer;
static char * input_write_pointer;


/*	Procedure: Read a character from the input stream
**	-------------------------------------------------
*/
PRIVATE char next_char(void)
{
    int status;
    if (input_read_pointer >= input_write_pointer) {
	status = read(s, input_buffer, INPUT_BUFFER_SIZE);	/*  Get some more data */
	if (status <= 0) return (char)-1;
	input_write_pointer = input_buffer + status;
	input_read_pointer = input_buffer;
    }
    return *input_read_pointer++;
}


//	Initialisaion for this class
//	----------------------------
//
//	We pick up the NewsHost name from, in order:
//
//	1.	WorldWideWeb
//	2.	Global
//	3.	News
//	4.	Defualt to cernvax.cern.ch	(!!!)

+ initialize
{
    const struct hostent  *phost;	    		/* Pointer to host - See netdb.h */
    struct sockaddr_in* sin = &soc_address;
    
/*  Set up defaults:
*/
    sin->sin_family = AF_INET;	    	/* Family = internet, host order  */
    sin->sin_port = NEWS_PORT;		    	/* Default: new port,    */

/*   Get name of Host
*/
    if ((NewsHost = NXGetDefaultValue("WorldWideWeb","NewsHost"))==0)
        if ((NewsHost = NXGetDefaultValue("News","NewsHost")) == 0)
	    NewsHost = "cernvax.cern.ch";

    if (*NewsHost>='0' && *NewsHost<='9') {   /* Numeric node address: */
	sin->sin_addr.s_addr = inet_addr((char *)NewsHost); /* See arpa/inet.h */

    } else {		    /* Alphanumeric node name: */
	phost=gethostbyname((char*)NewsHost);	/* See netdb.h */
	if (!phost) {
	    NXRunAlertPanel(NULL, "Can't find internet node name `%s'.",
	    	NULL,NULL,NULL,
		NewsHost);
	    CTRACE(tfp,
	      "NewsAccess: Can't find internet node name `%s'.\n",NewsHost);
	    return nil;  /* Fail */
	}
	memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
    }

    if (TRACE) printf( 
	"NewsAccess: Parsed address as port %4x, inet %d.%d.%d.%d\n",
		(unsigned int)ntohs(sin->sin_port),
		(int)*((unsigned char *)(&sin->sin_addr)+0),
		(int)*((unsigned char *)(&sin->sin_addr)+1),
		(int)*((unsigned char *)(&sin->sin_addr)+2),
		(int)*((unsigned char *)(&sin->sin_addr)+3));

    s=-1;		/* Disconnected */
    
    return self;
}


//	Return the name of the access
//	-----------------------------

- (const char *)name
{
    return "news";
}


//	Get Styles from stylesheet
//
static void get_styles()
{
    if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
    if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
}


/*	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(const char * command)
{
    int result;    
    char * p = response_text;
    if (command) {
        int status = write(s, command, strlen(command));
	if (status<0){
	    if (TRACE) printf(
	        "NewsAccess: Unable to send comand. Disconnecting.\n");
	    close(s);
	    s = -1;
	    return status;
	} /* if bad status */
	if (TRACE) printf("NNTP command sent: %s", command);
    } /* if command to be sent */
    
    for(;;) {  
	if (((*p++=NEXT_CHAR) == '\n') || (p == &response_text[LINE_LENGTH])) {
	    *p++=0;				/* Terminate the string */
	    if (TRACE) printf("NNTP Response: %s\n", response_text);
	    sscanf(response_text, "%i", &result);
	    return result;	    
	} /* if end of line */
	
	if (*(p-1) < 0) return -1;	/* 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.
//
static BOOL match(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,
//	THE EMAIL ADDRESS IS CORRUPTED
//
// 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 *s, *e;
    
    if ((s=index(email,'(')) && (e=index(email, ')')))
        if (e>s) {
	    *e=0;			/* Chop off everything after the ')'  */
	    return HTStrip(s+1);	/* Remove leading and trailing spaces */
	}
	
    if ((s=index(email,'<')) && (e=index(email, '>')))
        if (e>s) {
	    strcpy(s, e+1);		/* Remove <...> */
	    return HTStrip(email);	/* Remove leading and trailing spaces */
	}
	
    return HTStrip(email);		/* Default to the whole thing */
    

}


/*	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 refernce 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;
	strcpy(href,"news:");
	for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
        strncat(href, addr, p-addr);		/* Make complete hypertext reference */
    }
    
    [HT appendBeginAnchor:"" to:href];
    [HT appendText:text];
    [HT appendEndAnchor];
}

/*	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;
	write_anchor(start, start);
	*end = c;
	start = end;			/* Point to next one */
    }
}

/*	Read in an 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.
        
static void read_article()
{

    char line[LINE_LENGTH+1];
    char *references=NULL;			/* Hrefs for other articles */
    char *newsgroups=NULL;			/* Newsgroups list */
    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!=2)
#ifdef APPEND
    [HT appendStyle:addressStyle];
#else
    [HT applyStyle:addressStyle];
#endif
    while(!done){
	if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
	    *--p=0;				/* Terminate the string */
	    if (TRACE) printf("H %s\n", line);
	    if (line[0]=='.') {	
		if (line[1]<' ') {		/* End of article? */
		    done = YES;
		    break;
		}
	    
	    } else if (line[0]<' ') {
		break;		/* End of Header? */
	    } else if (match(line, "SUBJECT:")) {
		[HT setTitle:line+8];
	    } else if (match(line, "DATE:")
		   || match(line, "FROM:")
		   || match(line, "ORGANIZATION:")) {
		strcat(line, "\n");
#ifdef APPEND
		[HT appendText:index(line,':')+1];
#else
		[HT replaceSel:index(line,':')+1 style:addressStyle];
#endif		
	    } else if (match(line, "NEWSGROUPS:")) {
		StrAllocCopy(newsgroups, HTStrip(index(line,':')+1));
		
	    } else if (match(line, "REFERENCES:")) {
		StrAllocCopy(references, HTStrip(index(line,':')+1));
		
	    } /* end if match */
	    p = line;			/* Restart at beginning */
	} /* if end of line */
    } /* Loop over characters */

#ifdef APPEND
    [HT appendText:"\n"];
    [HT appendStyle:textStyle];
#else
    [HT replaceSel:"\n" style:addressStyle];
#endif    

    if (newsgroups) {
	[HT appendText: "\nNewsgroups: "];
	write_anchors(newsgroups);
	free(newsgroups);
    }
    
    if (references) {
	[HT appendText: "\nReferences: "];
	write_anchors(references);
	free(references);
    }

	[HT appendText: "\n\n\n"];

//	Read in the BODY of the Article:
//
    p = line;
    while(!done){
	if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
	    *p++=0;				/* Terminate the string */
	    if (TRACE) printf("B %s", line);
	    if (line[0]=='.') {
		if (line[1]<' ') {		/* End of article? */
		    done = YES;
		    break;
		} else {			/* Line starts with dot */
		    [HT appendText: &line[1]];	/* Ignore first dot */
		}
	    } else {

/*	Normal lines are scanned for buried references to other articles.
**	Unfortunately, it will pick up mail addresses as well!
*/
		char *l = line;
		char * p;
		while (p=index(l, '<')) {
		    char *q=index(l,'>');
		    if (q>p && index(p,'@')) {
		        char c = q[1];
			q[1] = 0;		/* chop up */
			*p = 0;
			[HT appendText:l];
			*p = '<'; 		/* again */
			*q = 0;
			[HT appendBeginAnchor:"" to:p+1];
			*q = '>'; 		/* again */
			[HT appendText:p];
			[HT appendEndAnchor];
			q[1] = c;		/* again */
			l=q+1;
		    } else break;		/* line has unmatched <> */
		} 
		[HT appendText: l];		/* Last bit of the line */
	    } /* if not dot */
	    p = line;				/* Restart at beginning */
	} /* if end of line */
    } /* Loop over characters */
}


/*	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 void read_list()
{

    char line[LINE_LENGTH+1];
    char *p;
    BOOL done = NO;
    
/*	Read in the HEADer of the article:
**
**	The header fields are either ignored, or formatted and put into the
**	Text.
*/
#ifdef APPEND
    [HT appendText: "\nNewsgroups:\n\n"];	/* Should be haeding style */
#else
    [HT replaceSel:"\nNewsgroups:\n\n" style:textStyle];	/* Should be heading */
#endif  
    p = line;
    while(!done){
	if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
	    *p++=0;				/* Terminate the string */
	    if (TRACE) printf("B %s", line);
	    if (line[0]=='.') {
		if (line[1]<' ') {		/* End of article? */
		    done = YES;
		    break;
		} else {			/* Line starts with dot */
#ifdef APPEND
		    [HT appendText: &line[1]];
#else
		    [HT replaceSel:&line[1] style:textStyle];	/* Ignore first dot */
#endif
		}
	    } else {

/*	Normal lines are scanned for references to newsgroups.
*/
		char group[LINE_LENGTH];
		int first, last;
		char postable;
		if (sscanf(line, "%s %i %i %c", group, &first, &last, &postable)==4)
		    write_anchor(line, group);
		else
#ifdef APPEND
		    [HT appendText:line];
#else
		    [HT replaceSel:line style:textStyle];
#endif
	    } /* if not dot */
	    p = line;			/* Restart at beginning */
	} /* if end of line */
    } /* Loop over characters */
}


/*	Read in a Newsgroup
**	-------------------
**	Unfortunately, we have to ask for each article one by one if we want more
**	than one field.
**
*/
void read_group(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 *p;
    BOOL done;

    char buffer[LINE_LENGTH];
    char *reference=0;			/* Href for article */
    int art;				/* Article number WITHIN GROUP */
    int status, count, first, last;	/* Response fields */
					/* count is only an upper limit */

    sscanf(response_text, " %i %i %i %i", &status, &count, &first, &last);
    if(TRACE) printf("Newsgroup status=%i, count=%i, (%i-%i) required:(%i-%i)\n",
    			status, count, first, last, first_required, last_required);
    if (last==0) {
        [HT appendText: "\nNo articles in this group.\n"];
	return;
    }
    
#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) {
        [HT appendText: "\nNo articles in this range.\n"];
	return;
    }

    if (last_required-first_required+1 > MAX_CHUNK) {	/* Trim this block */
        first_required = last_required-CHUNK_SIZE+1;
    }
    if (TRACE) printf ("    Chunk will be (%i-%i)\n", first_required, last_required);

/*	Link to earlier articles
*/
    if (first_required>first) {
    	int before;			/* Start of one before */
	if (first_required-MAX_CHUNK <= first) before = first;
	else before = first_required-CHUNK_SIZE;
    	sprintf(buffer, "%s/%i-%i", groupName, before, first_required-1);
	if (TRACE) printf("    Block before is %s\n", buffer);
	[HT appendBeginAnchor:"" to:buffer];
	[HT appendText: " (Earlier articles...)\n\n"];
	[HT appendEndAnchor];
    }
    
    done = NO;

/*#define USE_XHDR*/
#ifdef USE_XHDR
    if (count>FAST_THRESHOLD)  {
        sprintf(buffer,
	"\nThere are about %i articles currently available in %s, IDs as follows:\n\n",
		count, groupName); 
        [HT appendText:buffer];
        anchor_start = [HT textLength];
        sprintf(buffer, "XHDR Message-ID %i-%i\n", first, last);
	status = response(buffer);
	if (status==221) {

	    p = line;
	    while(!done){
		if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
		    *p++=0;				/* Terminate the string */
		    if (TRACE) printf("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

/*	Read newsgroup using individual fields:
*/
    if (!done) {
        if (first==first_required && last==last_required)
		[HT appendText:"\nAll available articles:\n\n"];
        else [HT appendText: "\nArticles:\n\n"];
	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 get 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) {
			sprintf(buffer, "HEAD %i\n", art);	/* Only one */
			status = response(buffer);
		    } else {					/* First of many */
			sprintf(buffer, "HEAD %i\nHEAD %i\n", art, art+1);
			status = response(buffer);
		    }
	    } else if (art==last_required) {			/* Last of many */
		    status = response(NULL);
	    } else {						/* Middle of many */
		    sprintf(buffer, "HEAD %i\n", art+1);
		    status = response(buffer);
	    }
#else
	    sprintf(buffer, "HEAD %i\n", art);
	    status = response(buffer);
#endif
	    if (status == 221) {	/* Head follows - parse it:*/
    
		p = line;				/* Write pointer */
		done = NO;
		while(!done){
		    if (   ((*p++=NEXT_CHAR) == '\n')
			|| (p == &line[LINE_LENGTH]) ) {
		    
			*--p=0;			/* Terminate  & chop LF*/
			p = line;			/* Restart at beginning */
			if (TRACE) printf("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+8);	/* Save author */
			    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:"))
				strcpy(author, author_name(index(line,':')+1));
			    break;
				    
			} /* end switch on first character */
		    } /* if end of line */
		} /* Loop over characters */
    
		sprintf(buffer, "\"%s\" - %s\n", subject, author);
		if (reference) {
		    write_anchor(buffer, reference);
		    free(reference);
		    reference=0;
		} else {
		    [HT appendText:buffer];
		}
		
    
/*	Change the title bar to indicate progress!
*/
		if (art%10 == 0) {
		    sprintf(buffer, "Reading newsgroup %s,  Article %i (of %i-%i) ...",
			    groupName, art, first, last);
		    [HT setTitle:buffer];
		}
    
	    } /* If good response */
	} /* Loop over article */	    
    } /* If read headers */
    
/*	Link to later articles
*/
    if (last_required<last) {
    	int after;			/* End of article after */
	after = last_required+CHUNK_SIZE;
    	if (after==last) sprintf(buffer, "news:%s", groupName);	/* original group */
    	else sprintf(buffer, "news:%s/%i-%i", groupName, last_required+1, after);
	if (TRACE) printf("    Block after is %s\n", buffer);
	[HT appendBeginAnchor:"" to:buffer];
	[HT appendText: "\n(Later articles...)\n"];
	[HT appendEndAnchor];
    }
    
/*	Set window title
*/
    sprintf(buffer, "Newsgroup %s,  Articles %i-%i",
    		groupName, first_required, last_required);
    [HT setTitle:buffer];

}


//	Open by name					-accessName:anchor:diagnostic:
//	------------
	
- accessName:(const char *)arg
	anchor:(Anchor *)anAnchor
	diagnostic:(int)diag
{
    char command[257];			/* The whole 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: group was asked for, not article */
    int first, last;			/* First and last articles asked for */

    diagnostic = diag;			/* set global flag */
    
    if (TRACE) printf("NewsAccess: Looking for %s\n", arg);
    get_styles();
    {
        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_wanted = (index(arg, '@')==0) && (index(arg, '*')==0);
	list_wanted  = (index(arg, '@')==0) && (index(arg, '*')!=0);
	
	p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
	if (list_wanted) {
	    strcpy(command, "LIST ");
	} 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, "%i-%i", &first, &last);
	    } else {
		strcpy(groupName, p1);
	    }
	    strcat(command, groupName);
	} else {
	    strcpy(command, "ARTICLE ");
	    if (index(p1, '<')==0) strcat(command,"<");
	    strcat(command, p1);
	    if (index(p1, '>')==0) strcat(command,">");
	}
	free(p1);

        strcat(command, "\r\n");		/* CR LF, as in rfc 977 */
	
    } /* scope of p1 */
    
    if (!*arg) return nil;			// Ignore if no name

    
//	Make a hypertext object with an anchor list.
        
    HT = [HyperText newAnchor:anAnchor Server:self];

    [HT setupWindow];			
    [HT selectText:self];		/* Replace everything with what's to come */
	
//	Now, let's get a stream setup up from the NewsHost:
        
    for(retries=0;retries<2; retries++){
    
        if (s<0) {
    	    [[HT window]setTitle:"Connecting to NewsHost ..."];	/* Tell user  */
	    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	    status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
	    if (status<0){
		char message[256];
	        close(s);
		s=-1;
		if (TRACE) printf("NewsAccess: Unable to connect to news host.\n");
/*		if (retries<=1) continue;   WHY TRY AGAIN ? 	*/
		NXRunAlertPanel(NULL,
    		    "Could not access newshost %s.",
		    NULL,NULL,NULL,
		    NewsHost);
		sprintf(message,
"\nCould not access %s.\n\n (Check default WorldWideWeb NewsHost ?)\n",
		    NewsHost);
		[HT setText:message];
		return HT;
	    } else {
		if (TRACE) printf("NewsAccess: Connected to news host %s.\n",
				NewsHost);
		if ((response(NULL) / 100) !=2) {
			close(s);
			s=-1;
			NXRunAlertPanel("News access",
			    "Could not retrieve information:\n   %s.",
			    NULL,NULL,NULL,
			    response_text);
    			[[HT window]setTitle: "News host response"];
			[HT setText:response_text];
			return HT;
		}
	    }
	} /* If needed opening */
	
        [[HT window]setTitle:arg];		/* Tell user something's happening */
	status = response(command);
	if (status<0) break;
	if ((status/ 100) !=2) {
	    NXRunAlertPanel("News access", response_text,
	    	NULL,NULL,NULL);
	    [HT setText:response_text];
	    close(s);
	    s=-1;
	    // return HT; -- no:the message might be "Timeout-disconnected" left over
	    continue;	//	Try again
	}
  
//	Load a group, article, etc
//
        [HT appendBegin];
	
	if (list_wanted) read_list();
	else if (group_wanted) read_group(groupName, first, last);
        else read_article();

	[HT appendEnd];

    	[HT setEditable:NO];		/* This is read-only data */
	[HT adjustWindow];
	return HT;
	
    } /* Retry loop */
    
    [HT setText:"Sorry, could not load requested news.\n"];
    
/*    NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
	    	NULL,NULL,NULL, arg);	No -- message earlier wil have covered it */
    return HT;
}

//		Actions:
//		=======


//	This will load an anchor which has a name
//	-----------------------------------------
//
// On entry,
//	Anchor's address is valid.
// On exit:
//	If there is no success, nil is returned.
//	Otherwise, the anchor is returned.
//

- loadAnchor: (Anchor *) a Diagnostic:(int)diagnostic
{
    HyperText * HT;

    if (![a node]) {
        HT = [self accessName:[a address] anchor:a diagnostic:diagnostic];
    	if (!HT) return nil;
	[[HT window] setDocEdited:NO];
    }
    return a;
}
@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.