1 /* NEWS ACCESS HTNews.c
5 ** 26 Sep 90 Written TBL
6 ** 29 Nov 91 Downgraded to C, for portable implementation.
9 #define NEWS_PORT 119 /* See rfc977 */
10 #define APPEND /* Use append methods */
11 #define MAX_CHUNK 40 /* Largest number of articles in one window */
12 #define CHUNK_SIZE 20 /* Number of articles for quick display */
14 #ifndef DEFAULT_NEWS_HOST
15 #define DEFAULT_NEWS_HOST "news"
18 #define SERVER_FILE "/usr/local/lib/rn/server"
22 #include "HTUtils.h" /* Coding convention macros */
32 #include <appkit/defaults.h>
33 #define NEWS_PROGRESS(foo)
35 #define NEWS_PROGRESS(foo) fprintf(stderr, "%s\n", (foo))
38 extern HTStyleSheet * styleSheet;
40 #define NEXT_CHAR HTGetChararcter()
41 #define LINE_LENGTH 512 /* Maximum length of line of ARTICLE etc */
42 #define GROUP_NAME_LENGTH 256 /* Maximum length of group name */
45 /* Module-wide variables
47 PRIVATE char * NewsHost;
48 PRIVATE struct sockaddr_in soc_address; /* Binary network address */
49 PRIVATE int s; /* Socket for NewsHost */
50 PRIVATE char response_text[LINE_LENGTH+1]; /* Last response */
51 PRIVATE HText * HT; /* the new hypertext */
52 PRIVATE HTParentAnchor *node_anchor; /* Its anchor */
53 PRIVATE int diagnostic; /* level: 0=none 2=source */
55 PRIVATE HTStyle *addressStyle; /* For address etc */
56 PRIVATE HTStyle *heading1Style; /* For heading level 1 */
57 PRIVATE HTStyle *textStyle; /* Text style */
60 /* Initialisation for this module
61 ** ------------------------------
63 ** Except on the NeXT, we pick up the NewsHost name from
65 ** 1. Environment variable NNTPSERVER
66 ** 2. File SERVER_FILE
67 ** 3. Compilation time macro DEFAULT_NEWS_HOST
68 ** 4. Default to "news"
70 ** On the NeXT, we pick up the NewsHost name from, in order:
72 ** 1. WorldWideWeb default "NewsHost"
73 ** 2. Global default "NewsHost"
74 ** 3. News default "NewsHost"
75 ** 4. Compilation time macro DEFAULT_NEWS_HOST
76 ** 5. Defualt to "news"
78 PRIVATE BOOL initialized = NO;
79 PRIVATE BOOL initialize NOARGS
81 CONST struct hostent *phost; /* Pointer to host - See netdb.h */
82 struct sockaddr_in* sin = &soc_address;
87 sin->sin_family = AF_INET; /* Family = internet, host order */
88 sin->sin_port = htons(NEWS_PORT); /* Default: new port, */
93 if ((NewsHost = NXGetDefaultValue("WorldWideWeb","NewsHost"))==0)
94 if ((NewsHost = NXGetDefaultValue("News","NewsHost")) == 0)
95 NewsHost = "cernvax.cern.ch";
97 if (getenv("NNTPSERVER")) {
98 StrAllocCopy(NewsHost, (char *)getenv("NNTPSERVER"));
99 if (TRACE) fprintf(stderr, "HTNews: NNTPSERVER defined as `%s'\n",
102 char server_name[256];
103 FILE* fp = fopen(SERVER_FILE, "r");
105 if (fscanf(fp, "%s", server_name)==1) {
106 StrAllocCopy(NewsHost, server_name);
107 if (TRACE) fprintf(stderr,
108 "HTNews: File %s defines news host as `%s'\n",
109 SERVER_FILE, NewsHost);
114 if (!NewsHost) NewsHost = DEFAULT_NEWS_HOST;
117 if (*NewsHost>='0' && *NewsHost<='9') { /* Numeric node address: */
118 sin->sin_addr.s_addr = inet_addr((char *)NewsHost); /* See arpa/inet.h */
120 } else { /* Alphanumeric node name: */
121 phost=gethostbyname((char*)NewsHost); /* See netdb.h */
124 NXRunAlertPanel(NULL, "Can't find internet node name `%s'.",
129 "HTNews: Can't find internet node name `%s'.\n",NewsHost);
132 "HTNews: Can't find internet node name `%s'.\n",NewsHost);
133 return NO; /* Fail */
135 memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
139 "HTNews: Parsed address as port %4x, inet %d.%d.%d.%d\n",
140 (unsigned int)ntohs(sin->sin_port),
141 (int)*((unsigned char *)(&sin->sin_addr)+0),
142 (int)*((unsigned char *)(&sin->sin_addr)+1),
143 (int)*((unsigned char *)(&sin->sin_addr)+2),
144 (int)*((unsigned char *)(&sin->sin_addr)+3));
146 s = -1; /* Disconnected */
153 /* Get Styles from stylesheet
154 ** --------------------------
156 PRIVATE void get_styles NOARGS
158 if (!heading1Style) heading1Style = HTStyleNamed(styleSheet, "Heading1");
159 if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
160 if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
164 /* Send NNTP Command line to remote host & Check Response
165 ** ------------------------------------------------------
168 ** command points to the command to be sent, including CRLF, or is null
169 ** pointer if no command to be sent.
171 ** Negative status indicates transmission error, socket closed.
172 ** Positive status is an NNTP status.
176 PRIVATE int response ARGS1(CONST char *,command)
179 char * p = response_text;
181 int status = NETWRITE(s, command, (int)strlen(command));
183 if (TRACE) fprintf(stderr,
184 "HTNews: Unable to send `%s'. Disconnecting.\n");
188 } /* if bad status */
189 if (TRACE) printf("NNTP command sent: %s", command);
190 } /* if command to be sent */
193 if (((*p++=NEXT_CHAR) == '\n') || (p == &response_text[LINE_LENGTH])) {
194 *p++=0; /* Terminate the string */
195 if (TRACE) printf("NNTP Response: %s\n", response_text);
196 sscanf(response_text, "%d", &result);
198 } /* if end of line */
201 if (TRACE) fprintf(stderr,
202 "HTNews: EOF on read, closing socket %d\n", s);
203 NETCLOSE(s); /* End of file, close socket */
204 return s = -1; /* End of file on response */
206 } /* Loop over characters */
210 /* Case insensitive string comparisons
211 ** -----------------------------------
214 ** template must be already un upper case.
215 ** unknown may be in upper or lower or mixed case to match.
217 PRIVATE BOOL match ARGS2 (CONST char *,unknown, CONST char *,template)
219 CONST char * u = unknown;
220 CONST char * t = template;
221 for (;*u && *t && (TOUPPER(*u)==*t); u++, t++) /* Find mismatch or end */ ;
222 return (BOOL)(*t==0); /* OK if end of template */
225 /* Find Author's name in mail address
226 ** ----------------------------------
229 ** THE EMAIL ADDRESS IS CORRUPTED
231 ** For example, returns "Tim Berners-Lee" if given any of
232 ** " Tim Berners-Lee <tim@online.cern.ch> "
233 ** or " tim@online.cern.ch ( Tim Berners-Lee ) "
235 PRIVATE char * author_name ARGS1 (char *,email)
239 if ((s=strchr(email,'(')) && (e=strchr(email, ')')))
241 *e=0; /* Chop off everything after the ')' */
242 return HTStrip(s+1); /* Remove leading and trailing spaces */
245 if ((s=strchr(email,'<')) && (e=strchr(email, '>')))
247 strcpy(s, e+1); /* Remove <...> */
248 return HTStrip(email); /* Remove leading and trailing spaces */
251 return HTStrip(email); /* Default to the whole thing */
256 /* Paste in an Anchor
257 ** ------------------
261 ** HT has a selection of zero length at the end.
262 ** text points to the text to be put into the file, 0 terminated.
263 ** addr points to the hypertext refernce address,
264 ** terminated by white space, comma, NULL or '>'
266 PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
268 char href[LINE_LENGTH+1];
272 strcpy(href,"news:");
273 for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
274 strncat(href, addr, p-addr); /* Make complete hypertext reference */
277 HText_beginAnchor(HT,
278 HTAnchor_findChildAndLink(node_anchor, "", href, 0));
279 HText_appendText(HT, text);
284 /* Write list of anchors
285 ** ---------------------
287 ** We take a pointer to a list of objects, and write out each,
288 ** generating an anchor for each.
291 ** HT has a selection of zero length at the end.
292 ** text points to a comma or space separated list of addresses.
294 ** *text is NOT any more chopped up into substrings.
296 PRIVATE void write_anchors ARGS1 (char *,text)
302 for(;*start && (WHITE(*start)); start++); /* Find start */
303 if (!*start) return; /* (Done) */
304 for(end=start; *end && (*end!=' ') && (*end!=','); end++);/* Find end */
305 if (*end) end++; /* Include comma or space but not NULL */
308 write_anchor(start, start);
310 start = end; /* Point to next one */
314 /* Abort the connection abort_socket
315 ** --------------------
317 PRIVATE void abort_socket NOARGS
319 if (TRACE) fprintf(stderr,
320 "HTNews: EOF on read, closing socket %d\n", s);
321 NETCLOSE(s); /* End of file, close socket */
322 HText_appendText(HT, "Network Error: connection lost");
323 HText_appendParagraph(HT);
324 s = -1; /* End of file on response */
328 /* Read in an Article read_article
329 ** ------------------
332 ** Note the termination condition of a single dot on a line by itself.
333 ** RFC 977 specifies that the line "folding" of RFC850 is not used, so we
334 ** do not handle it here.
337 ** s Global socket number is OK
338 ** HT Global hypertext object is ready for appending text
340 PRIVATE void read_article NOARGS
343 char line[LINE_LENGTH+1];
344 char *references=NULL; /* Hrefs for other articles */
345 char *newsgroups=NULL; /* Newsgroups list */
349 /* Read in the HEADer of the article:
351 ** The header fields are either ignored, or formatted and put into the
355 HText_setStyle(HT, addressStyle);
357 char ch = *p++ = NEXT_CHAR;
359 abort_socket(); /* End of file, close socket */
360 return; /* End of file on response */
362 if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
363 *--p=0; /* Terminate the string */
364 if (TRACE) printf("H %s\n", line);
367 if (line[1]<' ') { /* End of article? */
372 } else if (line[0]<' ') {
373 break; /* End of Header? */
374 } else if (match(line, "SUBJECT:")) {
375 HTAnchor_setTitle(node_anchor, line+8);
376 HText_setStyle(HT, heading1Style);
377 HText_appendText(HT, line+8);
378 HText_setStyle(HT, addressStyle);
379 } else if (match(line, "DATE:")
380 || match(line, "FROM:")
381 || match(line, "ORGANIZATION:")) {
383 HText_appendText(HT, strchr(line,':')+1);
384 } else if (match(line, "NEWSGROUPS:")) {
385 StrAllocCopy(newsgroups, HTStrip(strchr(line,':')+1));
387 } else if (match(line, "REFERENCES:")) {
388 StrAllocCopy(references, HTStrip(strchr(line,':')+1));
391 p = line; /* Restart at beginning */
392 } /* if end of line */
393 } /* Loop over characters */
395 HText_appendCharacter(HT, '\n');
396 HText_setStyle(HT, textStyle);
398 HText_appendText(HT, "\nNewsgroups: ");
399 write_anchors(newsgroups);
404 HText_appendText(HT, "\nReferences: ");
405 write_anchors(references);
409 HText_appendText(HT, "\n\n\n");
411 } else { /* diagnostic */
412 HText_setStyle(HT, textStyle);
415 /* Read in the BODY of the Article:
419 char ch = *p++ = NEXT_CHAR;
421 abort_socket(); /* End of file, close socket */
422 return; /* End of file on response */
424 if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
425 *p++=0; /* Terminate the string */
426 if (TRACE) printf("B %s", line);
428 if (line[1]<' ') { /* End of article? */
431 } else { /* Line starts with dot */
432 HText_appendText(HT, &line[1]); /* Ignore first dot */
436 /* Normal lines are scanned for buried references to other articles.
437 ** Unfortunately, it will pick up mail addresses as well!
441 while (p=strchr(l, '<')) {
442 char *q = strchr(p,'>');
443 char *at = strchr(p, '@');
444 if (q && at && at<q) {
446 q[1] = 0; /* chop up */
448 HText_appendText(HT, l);
449 *p = '<'; /* again */
451 HText_beginAnchor(HT,
452 HTAnchor_findChildAndLink(
453 node_anchor, "", p+1, 0));
454 *q = '>'; /* again */
455 HText_appendText(HT, p);
457 q[1] = c; /* again */
459 } else break; /* line has unmatched <> */
461 HText_appendText(HT, l); /* Last bit of the line */
463 p = line; /* Restart at beginning */
464 } /* if end of line */
465 } /* Loop over characters */
469 /* Read in a List of Newsgroups
470 ** ----------------------------
473 ** Note the termination condition of a single dot on a line by itself.
474 ** RFC 977 specifies that the line "folding" of RFC850 is not used, so we
475 ** do not handle it here.
477 PRIVATE void read_list NOARGS
480 char line[LINE_LENGTH+1];
484 /* Read in the HEADer of the article:
486 ** The header fields are either ignored, or formatted and put into the
489 HText_appendText(HT, "\nNewsgroups:\n\n"); /* Should be haeding style */
492 char ch = *p++ = NEXT_CHAR;
494 abort_socket(); /* End of file, close socket */
495 return; /* End of file on response */
497 if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
498 *p++=0; /* Terminate the string */
499 if (TRACE) printf("B %s", line);
501 if (line[1]<' ') { /* End of article? */
504 } else { /* Line starts with dot */
505 HText_appendText(HT, &line[1]);
509 /* Normal lines are scanned for references to newsgroups.
511 char group[LINE_LENGTH];
514 if (sscanf(line, "%s %d %d %c", group, &first, &last, &postable)==4)
515 write_anchor(line, group);
517 HText_appendText(HT, line);
519 p = line; /* Restart at beginning */
520 } /* if end of line */
521 } /* Loop over characters */
525 /* Read in a Newsgroup
526 ** -------------------
527 ** Unfortunately, we have to ask for each article one by one if we
528 ** want more than one field.
531 PRIVATE void read_group ARGS3(
532 CONST char *,groupName,
537 char line[LINE_LENGTH+1];
538 char author[LINE_LENGTH+1];
539 char subject[LINE_LENGTH+1];
543 char buffer[LINE_LENGTH];
544 char *reference=0; /* Href for article */
545 int art; /* Article number WITHIN GROUP */
546 int status, count, first, last; /* Response fields */
547 /* count is only an upper limit */
549 sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
550 if(TRACE) printf("Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
551 status, count, first, last, first_required, last_required);
553 HText_appendText(HT, "\nNo articles in this group.\n");
557 #define FAST_THRESHOLD 100 /* Above this, read IDs fast */
558 #define CHOP_THRESHOLD 50 /* Above this, chop off the rest */
560 if (first_required<first) first_required = first; /* clip */
561 if ((last_required==0) || (last_required > last)) last_required = last;
563 if (last_required<=first_required) {
564 HText_appendText(HT, "\nNo articles in this range.\n");
568 if (last_required-first_required+1 > MAX_CHUNK) { /* Trim this block */
569 first_required = last_required-CHUNK_SIZE+1;
572 " Chunk will be (%d-%d)\n", first_required, last_required);
574 /* Link to earlier articles
576 if (first_required>first) {
577 int before; /* Start of one before */
578 if (first_required-MAX_CHUNK <= first) before = first;
579 else before = first_required-CHUNK_SIZE;
580 sprintf(buffer, "%s/%d-%d", groupName, before, first_required-1);
581 if (TRACE) printf(" Block before is %s\n", buffer);
582 HText_appendText(HT, " (");
583 HText_beginAnchor(HT,
584 HTAnchor_findChildAndLink(node_anchor, "", buffer, 0));
585 HText_appendText(HT, "Earlier articles");
587 HText_appendText(HT, "...)\n");
594 if (count>FAST_THRESHOLD) {
596 "\nThere are about %d articles currently available in %s, IDs as follows:\n\n",
598 HText_appendText(HT, buffer);
599 sprintf(buffer, "XHDR Message-ID %d-%d\n", first, last);
600 status = response(buffer);
605 char ch = *p++ = NEXT_CHAR;
607 abort_socket(); /* End of file, close socket */
608 return; /* End of file on response */
610 if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
611 *p++=0; /* Terminate the string */
612 if (TRACE) printf("X %s", line);
614 if (line[1]<' ') { /* End of article? */
617 } else { /* Line starts with dot */
618 /* Ignore strange line */
622 /* Normal lines are scanned for references to articles.
624 char * space = strchr(line, ' ');
626 write_anchor(space, space);
628 p = line; /* Restart at beginning */
629 } /* if end of line */
630 } /* Loop over characters */
632 /* leaving loop with "done" set */
637 /* Read newsgroup using individual fields:
640 if (first==first_required && last==last_required)
641 HText_appendText(HT, "\nAll available articles in ");
642 else HText_appendText(HT, "\nArticles in ");
643 HText_appendText(HT, groupName);
644 HText_appendText(HT, "\n\n");
645 for(art=first_required; art<=last_required; art++) {
649 /* With this code we try to keep the server running flat out by queuing just
650 ** one extra command ahead of time. We assume (1) that the server won't abort
651 ** if it get input during output, and (2) that TCP buffering is enough for the
652 ** two commands. Both these assumptions seem very reasonable. However, we HAVE
653 ** had a hangup with a loaded server.
655 if (art==first_required) {
656 if (art==last_required) {
657 sprintf(buffer, "HEAD %d\n", art); /* Only one */
658 status = response(buffer);
659 } else { /* First of many */
660 sprintf(buffer, "HEAD %d\nHEAD %d\n", art, art+1);
661 status = response(buffer);
663 } else if (art==last_required) { /* Last of many */
664 status = response(NULL);
665 } else { /* Middle of many */
666 sprintf(buffer, "HEAD %d\n", art+1);
667 status = response(buffer);
670 #else /* NOT OVERLAP */
671 sprintf(buffer, "HEAD %d\n", art);
672 status = response(buffer);
673 #endif /* NOT OVERLAP */
675 if (status == 221) { /* Head follows - parse it:*/
677 p = line; /* Write pointer */
680 char ch = *p++ = NEXT_CHAR;
682 abort_socket(); /* End of file, close socket */
683 return; /* End of file on response */
686 || (p == &line[LINE_LENGTH]) ) {
688 *--p=0; /* Terminate & chop LF*/
689 p = line; /* Restart at beginning */
690 if (TRACE) printf("G %s\n", line);
694 done = (line[1]<' '); /* End of article? */
699 if (match(line, "SUBJECT:"))
700 strcpy(subject, line+9);/* Save subject */
705 if (match(line, "MESSAGE-ID:")) {
706 char * addr = HTStrip(line+11) +1; /* Chop < */
707 addr[strlen(addr)-1]=0; /* Chop > */
708 StrAllocCopy(reference, addr);
714 if (match(line, "FROM:")) {
717 author_name(strchr(line,':')+1));
718 p = author + strlen(author) - 1;
719 if (*p=='\n') *p = 0; /* Chop off newline */
723 } /* end switch on first character */
724 } /* if end of line */
725 } /* Loop over characters */
727 sprintf(buffer, "\"%s\" - %s", subject, author);
729 write_anchor(buffer, reference);
733 HText_appendText(HT, buffer);
735 HText_appendParagraph(HT);
738 /* Change the title bar to indicate progress!
741 sprintf(buffer, "Reading newsgroup %s, Article %d (of %d-%d) ...",
742 groupName, art, first, last);
743 HTAnchor_setTitle(node_anchor, buffer);
746 } /* If good response */
747 } /* Loop over article */
748 } /* If read headers */
750 /* Link to later articles
752 if (last_required<last) {
753 int after; /* End of article after */
754 after = last_required+CHUNK_SIZE;
755 if (after==last) sprintf(buffer, "news:%s", groupName); /* original group */
756 else sprintf(buffer, "news:%s/%d-%d", groupName, last_required+1, after);
757 if (TRACE) printf(" Block after is %s\n", buffer);
758 HText_appendText(HT, "(");
759 HText_beginAnchor(HT, HTAnchor_findChildAndLink(
760 node_anchor, "", buffer, 0));
761 HText_appendText(HT, "Later articles");
763 HText_appendText(HT, "...)\n");
768 sprintf(buffer, "Newsgroup %s, Articles %d-%d",
769 groupName, first_required, last_required);
770 HTAnchor_setTitle(node_anchor, buffer);
775 /* Load by name HTLoadNews
778 PUBLIC int HTLoadNews ARGS3(
780 HTParentAnchor *,anAnchor,
783 char command[257]; /* The whole command */
784 char groupName[GROUP_NAME_LENGTH]; /* Just the group name */
785 int status; /* tcp return */
786 int retries; /* A count of how hard we have tried */
787 BOOL group_wanted; /* Flag: group was asked for, not article */
788 BOOL list_wanted; /* Flag: group was asked for, not article */
789 int first, last; /* First and last articles asked for */
791 diagnostic = diag; /* set global flag */
793 if (TRACE) printf("HTNews: Looking for %s\n", arg);
796 if (!initialized) initialized = initialize();
797 if (!initialized) return -1; /* FAIL */
802 /* We will ask for the document, omitting the host name & anchor.
804 ** Syntax of address is
806 ** <xxx@yyy> Same article
807 ** xxxxx News group (no "@")
809 group_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')==0);
810 list_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')!=0);
812 p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
814 strcpy(command, "LIST ");
815 } else if (group_wanted) {
816 char * slash = strchr(p1, '/');
817 strcpy(command, "GROUP ");
822 strcpy(groupName, p1);
824 (void) sscanf(slash+1, "%d-%d", &first, &last);
826 strcpy(groupName, p1);
828 strcat(command, groupName);
830 strcpy(command, "ARTICLE ");
831 if (strchr(p1, '<')==0) strcat(command,"<");
833 if (strchr(p1, '>')==0) strcat(command,">");
837 strcat(command, "\r\n"); /* CR LF, as in rfc 977 */
841 if (!*arg) return NO; /* Ignore if no name */
844 /* Make a hypertext object with an anchor list.
846 node_anchor = anAnchor;
847 HT = HText_new(anAnchor);
848 HText_beginAppend(HT);
850 /* Now, let's get a stream setup up from the NewsHost:
852 for(retries=0;retries<2; retries++){
855 HTAnchor_setTitle(node_anchor, "Connecting to NewsHost ...");/* Tell user */
856 NEWS_PROGRESS("Connecting to NewsHost ...");
857 s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
858 status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
863 if (TRACE) printf("HTNews: Unable to connect to news host.\n");
864 /* if (retries<=1) continue; WHY TRY AGAIN ? */
866 NXRunAlertPanel(NULL,
867 "Could not access newshost %s.",
871 fprintf(stderr, "Could not access newshost %s\n",
875 "\nCould not access %s.\n\n (Check default WorldWideWeb NewsHost ?)\n",
877 HText_beginAppend(HT);
878 HText_appendText(HT, message);
882 if (TRACE) printf("HTNews: Connected to news host %s.\n",
884 HTInitInput(s); /* set up buffering */
885 if ((response(NULL) / 100) !=2) {
889 NXRunAlertPanel("News access",
890 "Could not retrieve information:\n %s.",
894 HTAnchor_setTitle(node_anchor, "News host response");
895 HText_beginAppend(HT);
897 "Sorry, could not retrieve information: ");
898 HText_appendText(HT, response_text);
903 } /* If needed opening */
905 HTAnchor_setTitle(node_anchor, arg);/* Tell user something's happening */
906 status = response(command);
908 if ((status/ 100) !=2) {
909 /* NXRunAlertPanel("News access", response_text,
912 HText_beginAppend(HT);
913 HText_appendText(HT, response_text);
917 /* return HT; -- no:the message might be "Timeout-disconnected" left over */
918 continue; /* Try again */
921 /* Load a group, article, etc
923 HText_beginAppend(HT);
925 if (list_wanted) read_list();
926 else if (group_wanted) read_group(groupName, first, last);
934 HText_beginAppend(HT);
935 HText_appendText(HT, "Sorry, could not load requested news.\n");
938 /* NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
939 NULL,NULL,NULL, arg);No -- message earlier wil have covered it */