Works a bit more
[erwise] / Cl / WWWLibrary / HTNews.c
1 /*                      NEWS ACCESS                             HTNews.c
2 **                      ===========
3 **
4 ** History:
5 **      26 Sep 90       Written TBL
6 **      29 Nov 91       Downgraded to C, for portable implementation.
7 */
8
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 */
13
14 #ifndef DEFAULT_NEWS_HOST
15 #define DEFAULT_NEWS_HOST "news"
16 #endif
17 #ifndef SERVER_FILE
18 #define SERVER_FILE "/usr/local/lib/rn/server"
19 #endif
20
21 #include <ctype.h>
22 #include "HTUtils.h"            /* Coding convention macros */
23 #include "tcp.h"
24
25 #include "HTNews.h"
26
27 #include "HText.h"
28 #include "HTParse.h"
29 #include "HTFormat.h"
30
31 #ifdef NeXTStep
32 #include <appkit/defaults.h>
33 #define NEWS_PROGRESS(foo)
34 #else
35 #define NEWS_PROGRESS(foo) fprintf(stderr, "%s\n", (foo))
36 #endif
37
38 extern HTStyleSheet * styleSheet;
39
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 */
43
44
45 /*      Module-wide variables
46 */
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 */
54
55 PRIVATE HTStyle *addressStyle;                  /* For address etc */
56 PRIVATE HTStyle *heading1Style;                 /* For heading level 1 */
57 PRIVATE HTStyle *textStyle;                     /* Text style */
58
59
60 /*      Initialisation for this module
61 **      ------------------------------
62 **
63 **      Except on the NeXT, we pick up the NewsHost name from
64 **
65 **      1.      Environment variable NNTPSERVER
66 **      2.      File SERVER_FILE
67 **      3.      Compilation time macro DEFAULT_NEWS_HOST
68 **      4.      Default to "news"
69 **
70 **      On the NeXT, we pick up the NewsHost name from, in order:
71 **
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"
77 */
78 PRIVATE BOOL initialized = NO;
79 PRIVATE BOOL initialize NOARGS
80 {
81     CONST struct hostent  *phost;         /* Pointer to host - See netdb.h */
82     struct sockaddr_in* sin = &soc_address;
83
84         
85 /*  Set up defaults:
86 */
87     sin->sin_family = AF_INET;          /* Family = internet, host order  */
88     sin->sin_port = htons(NEWS_PORT);   /* Default: new port,    */
89
90 /*   Get name of Host
91 */
92 #ifdef NeXTStep
93     if ((NewsHost = NXGetDefaultValue("WorldWideWeb","NewsHost"))==0)
94         if ((NewsHost = NXGetDefaultValue("News","NewsHost")) == 0)
95             NewsHost = "cernvax.cern.ch";
96 #else
97     if (getenv("NNTPSERVER")) {
98         StrAllocCopy(NewsHost, (char *)getenv("NNTPSERVER"));
99         if (TRACE) fprintf(stderr, "HTNews: NNTPSERVER defined as `%s'\n",
100                 NewsHost);
101     } else {
102         char server_name[256];
103         FILE* fp = fopen(SERVER_FILE, "r");
104         if (fp) {
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);
110             }
111             fclose(fp);
112         }
113     }
114     if (!NewsHost) NewsHost = DEFAULT_NEWS_HOST;
115 #endif
116
117     if (*NewsHost>='0' && *NewsHost<='9') {   /* Numeric node address: */
118         sin->sin_addr.s_addr = inet_addr((char *)NewsHost); /* See arpa/inet.h */
119
120     } else {                /* Alphanumeric node name: */
121         phost=gethostbyname((char*)NewsHost);   /* See netdb.h */
122         if (!phost) {
123 #ifdef NeXTStep
124             NXRunAlertPanel(NULL, "Can't find internet node name `%s'.",
125                 NULL,NULL,NULL,
126                 NewsHost);
127 #else
128             fprintf(stderr,
129               "HTNews: Can't find internet node name `%s'.\n",NewsHost);
130 #endif
131             CTRACE(tfp,
132               "HTNews: Can't find internet node name `%s'.\n",NewsHost);
133             return NO;  /* Fail */
134         }
135         memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
136     }
137
138     if (TRACE) printf( 
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));
145
146     s = -1;             /* Disconnected */
147     
148     return YES;
149 }
150
151
152
153 /*      Get Styles from stylesheet
154 **      --------------------------
155 */
156 PRIVATE void get_styles NOARGS
157 {
158     if (!heading1Style) heading1Style = HTStyleNamed(styleSheet, "Heading1");
159     if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
160     if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
161 }
162
163
164 /*      Send NNTP Command line to remote host & Check Response
165 **      ------------------------------------------------------
166 **
167 ** On entry,
168 **      command points to the command to be sent, including CRLF, or is null
169 **              pointer if no command to be sent.
170 ** On exit,
171 **      Negative status indicates transmission error, socket closed.
172 **      Positive status is an NNTP status.
173 */
174
175
176 PRIVATE int response ARGS1(CONST char *,command)
177 {
178     int result;    
179     char * p = response_text;
180     if (command) {
181         int status = NETWRITE(s, command, (int)strlen(command));
182         if (status<0){
183             if (TRACE) fprintf(stderr,
184                 "HTNews: Unable to send `%s'. Disconnecting.\n");
185             NETCLOSE(s);
186             s = -1;
187             return status;
188         } /* if bad status */
189         if (TRACE) printf("NNTP command sent: %s", command);
190     } /* if command to be sent */
191     
192     for(;;) {  
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);
197             return result;          
198         } /* if end of line */
199         
200         if (*(p-1) < 0) {
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 */
205         }
206     } /* Loop over characters */
207 }
208
209
210 /*      Case insensitive string comparisons
211 **      -----------------------------------
212 **
213 ** On entry,
214 **      template must be already un upper case.
215 **      unknown may be in upper or lower or mixed case to match.
216 */
217 PRIVATE BOOL match ARGS2 (CONST char *,unknown, CONST char *,template)
218 {
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 */
223 }
224
225 /*      Find Author's name in mail address
226 **      ----------------------------------
227 **
228 ** On exit,
229 **      THE EMAIL ADDRESS IS CORRUPTED
230 **
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 ) "
234 */
235 PRIVATE char * author_name ARGS1 (char *,email)
236 {
237     char *s, *e;
238     
239     if ((s=strchr(email,'(')) && (e=strchr(email, ')')))
240         if (e>s) {
241             *e=0;                       /* Chop off everything after the ')'  */
242             return HTStrip(s+1);        /* Remove leading and trailing spaces */
243         }
244         
245     if ((s=strchr(email,'<')) && (e=strchr(email, '>')))
246         if (e>s) {
247             strcpy(s, e+1);             /* Remove <...> */
248             return HTStrip(email);      /* Remove leading and trailing spaces */
249         }
250         
251     return HTStrip(email);              /* Default to the whole thing */
252
253 }
254
255
256 /*      Paste in an Anchor
257 **      ------------------
258 **
259 **
260 ** On entry,
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 '>' 
265 */
266 PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
267 {
268     char href[LINE_LENGTH+1];
269                 
270     {
271         CONST char * p;
272         strcpy(href,"news:");
273         for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
274         strncat(href, addr, p-addr);    /* Make complete hypertext reference */
275     }
276     
277     HText_beginAnchor(HT,
278                 HTAnchor_findChildAndLink(node_anchor, "",  href, 0));
279     HText_appendText(HT, text);
280     HText_endAnchor(HT);
281 }
282
283
284 /*      Write list of anchors
285 **      ---------------------
286 **
287 **      We take a pointer to a list of objects, and write out each,
288 **      generating an anchor for each.
289 **
290 ** On entry,
291 **      HT      has a selection of zero length at the end.
292 **      text    points to a comma or space separated list of addresses.
293 ** On exit,
294 **      *text   is NOT any more chopped up into substrings.
295 */
296 PRIVATE void write_anchors ARGS1 (char *,text)
297 {
298     char * start = text;
299     char * end;
300     char c;
301     for (;;) {
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 */
306         c = *end;
307         *end = 0;
308         write_anchor(start, start);
309         *end = c;
310         start = end;                    /* Point to next one */
311     }
312 }
313
314 /*      Abort the connection                                    abort_socket
315 **      --------------------
316 */
317 PRIVATE void abort_socket NOARGS
318 {
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 */
325     return;
326 }
327
328 /*      Read in an Article                                      read_article
329 **      ------------------
330 **
331 **
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.
335 **
336 ** On entry,
337 **      s       Global socket number is OK
338 **      HT      Global hypertext object is ready for appending text
339 */       
340 PRIVATE void read_article NOARGS
341 {
342
343     char line[LINE_LENGTH+1];
344     char *references=NULL;                      /* Hrefs for other articles */
345     char *newsgroups=NULL;                      /* Newsgroups list */
346     char *p = line;
347     BOOL done = NO;
348     
349 /*      Read in the HEADer of the article:
350 **
351 **      The header fields are either ignored, or formatted and put into the
352 **       Text.
353 */
354     if (!diagnostic) {
355         HText_setStyle(HT, addressStyle);
356         while(!done){
357             char ch = *p++ = NEXT_CHAR;
358             if (ch==(char)EOF) {
359                 abort_socket(); /* End of file, close socket */
360                 return;         /* End of file on response */
361             }
362             if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
363                 *--p=0;                         /* Terminate the string */
364                 if (TRACE) printf("H %s\n", line);
365
366                 if (line[0]=='.') {     
367                     if (line[1]<' ') {          /* End of article? */
368                         done = YES;
369                         break;
370                     }
371                 
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:")) {
382                     strcat(line, "\n");
383                     HText_appendText(HT, strchr(line,':')+1);
384                 } else if (match(line, "NEWSGROUPS:")) {
385                     StrAllocCopy(newsgroups, HTStrip(strchr(line,':')+1));
386                     
387                 } else if (match(line, "REFERENCES:")) {
388                     StrAllocCopy(references, HTStrip(strchr(line,':')+1));
389                     
390                 } /* end if match */
391                 p = line;                       /* Restart at beginning */
392             } /* if end of line */
393         } /* Loop over characters */
394     
395         HText_appendCharacter(HT, '\n');
396         HText_setStyle(HT, textStyle);
397         if (newsgroups) {
398             HText_appendText(HT, "\nNewsgroups: ");
399             write_anchors(newsgroups);
400             free(newsgroups);
401         }
402         
403         if (references) {
404             HText_appendText(HT, "\nReferences: ");
405             write_anchors(references);
406             free(references);
407         }
408     
409         HText_appendText(HT, "\n\n\n");
410         
411     } else { /* diagnostic */
412         HText_setStyle(HT, textStyle);
413     }
414     
415 /*      Read in the BODY of the Article:
416 */
417     p = line;
418     while(!done){
419         char ch = *p++ = NEXT_CHAR;
420         if (ch==(char)EOF) {
421             abort_socket();     /* End of file, close socket */
422             return;             /* End of file on response */
423         }
424         if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
425             *p++=0;                             /* Terminate the string */
426             if (TRACE) printf("B %s", line);
427             if (line[0]=='.') {
428                 if (line[1]<' ') {              /* End of article? */
429                     done = YES;
430                     break;
431                 } else {                        /* Line starts with dot */
432                     HText_appendText(HT, &line[1]);     /* Ignore first dot */
433                 }
434             } else {
435
436 /*      Normal lines are scanned for buried references to other articles.
437 **      Unfortunately, it will pick up mail addresses as well!
438 */
439                 char *l = line;
440                 char * p;
441                 while (p=strchr(l, '<')) {
442                     char *q  = strchr(p,'>');
443                     char *at = strchr(p, '@');
444                     if (q && at && at<q) {
445                         char c = q[1];
446                         q[1] = 0;               /* chop up */
447                         *p = 0;
448                         HText_appendText(HT, l);
449                         *p = '<';               /* again */
450                         *q = 0;
451                         HText_beginAnchor(HT,
452                             HTAnchor_findChildAndLink(
453                                 node_anchor, "", p+1, 0));
454                         *q = '>';               /* again */
455                         HText_appendText(HT, p);
456                         HText_endAnchor(HT);
457                         q[1] = c;               /* again */
458                         l=q+1;
459                     } else break;               /* line has unmatched <> */
460                 } 
461                 HText_appendText(HT,  l);       /* Last bit of the line */
462             } /* if not dot */
463             p = line;                           /* Restart at beginning */
464         } /* if end of line */
465     } /* Loop over characters */
466 }
467
468
469 /*      Read in a List of Newsgroups
470 **      ----------------------------
471 */
472 /*
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.
476 */        
477 PRIVATE void read_list NOARGS
478 {
479
480     char line[LINE_LENGTH+1];
481     char *p;
482     BOOL done = NO;
483     
484 /*      Read in the HEADer of the article:
485 **
486 **      The header fields are either ignored, or formatted and put into the
487 **      Text.
488 */
489     HText_appendText(HT,  "\nNewsgroups:\n\n"); /* Should be haeding style */
490     p = line;
491     while(!done){
492         char ch = *p++ = NEXT_CHAR;
493         if (ch==(char)EOF) {
494             abort_socket();     /* End of file, close socket */
495             return;             /* End of file on response */
496         }
497         if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
498             *p++=0;                             /* Terminate the string */
499             if (TRACE) printf("B %s", line);
500             if (line[0]=='.') {
501                 if (line[1]<' ') {              /* End of article? */
502                     done = YES;
503                     break;
504                 } else {                        /* Line starts with dot */
505                     HText_appendText(HT,  &line[1]);
506                 }
507             } else {
508
509 /*      Normal lines are scanned for references to newsgroups.
510 */
511                 char group[LINE_LENGTH];
512                 int first, last;
513                 char postable;
514                 if (sscanf(line, "%s %d %d %c", group, &first, &last, &postable)==4)
515                     write_anchor(line, group);
516                 else
517                     HText_appendText(HT, line);
518             } /* if not dot */
519             p = line;                   /* Restart at beginning */
520         } /* if end of line */
521     } /* Loop over characters */
522 }
523
524
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.
529 **
530 */
531 PRIVATE void read_group ARGS3(
532   CONST char *,groupName,
533   int,first_required,
534   int,last_required
535 )
536 {
537     char line[LINE_LENGTH+1];
538     char author[LINE_LENGTH+1];
539     char subject[LINE_LENGTH+1];
540     char *p;
541     BOOL done;
542
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 */
548
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);
552     if (last==0) {
553         HText_appendText(HT,  "\nNo articles in this group.\n");
554         return;
555     }
556     
557 #define FAST_THRESHOLD 100      /* Above this, read IDs fast */
558 #define CHOP_THRESHOLD 50       /* Above this, chop off the rest */
559
560     if (first_required<first) first_required = first;           /* clip */
561     if ((last_required==0) || (last_required > last)) last_required = last;
562     
563     if (last_required<=first_required) {
564         HText_appendText(HT,  "\nNo articles in this range.\n");
565         return;
566     }
567
568     if (last_required-first_required+1 > MAX_CHUNK) {   /* Trim this block */
569         first_required = last_required-CHUNK_SIZE+1;
570     }
571     if (TRACE) printf (
572     "    Chunk will be (%d-%d)\n", first_required, last_required);
573
574 /*      Link to earlier articles
575 */
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");
586         HText_endAnchor(HT);
587         HText_appendText(HT,  "...)\n");
588     }
589     
590     done = NO;
591
592 /*#define USE_XHDR*/
593 #ifdef USE_XHDR
594     if (count>FAST_THRESHOLD)  {
595         sprintf(buffer,
596         "\nThere are about %d articles currently available in %s, IDs as follows:\n\n",
597                 count, groupName); 
598         HText_appendText(HT, buffer);
599         sprintf(buffer, "XHDR Message-ID %d-%d\n", first, last);
600         status = response(buffer);
601         if (status==221) {
602
603             p = line;
604             while(!done){
605                 char ch = *p++ = NEXT_CHAR;
606                 if (ch==(char)EOF) {
607                     abort_socket();     /* End of file, close socket */
608                     return;             /* End of file on response */
609                 }
610                 if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
611                     *p++=0;                             /* Terminate the string */
612                     if (TRACE) printf("X %s", line);
613                     if (line[0]=='.') {
614                         if (line[1]<' ') {              /* End of article? */
615                             done = YES;
616                             break;
617                         } else {                        /* Line starts with dot */
618                                 /* Ignore strange line */
619                         }
620                     } else {
621         
622         /*      Normal lines are scanned for references to articles.
623         */
624                         char * space = strchr(line, ' ');
625                         if (space++)
626                             write_anchor(space, space);
627                     } /* if not dot */
628                     p = line;                   /* Restart at beginning */
629                 } /* if end of line */
630             } /* Loop over characters */
631
632             /* leaving loop with "done" set */
633         } /* Good status */
634     };
635 #endif
636
637 /*      Read newsgroup using individual fields:
638 */
639     if (!done) {
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++) {
646     
647 /*#define OVERLAP*/
648 #ifdef OVERLAP
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.
654 */
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);
662                     }
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);
668             }
669             
670 #else   /* NOT OVERLAP */
671             sprintf(buffer, "HEAD %d\n", art);
672             status = response(buffer);
673 #endif  /* NOT OVERLAP */
674
675             if (status == 221) {        /* Head follows - parse it:*/
676     
677                 p = line;                               /* Write pointer */
678                 done = NO;
679                 while(!done){
680                     char ch = *p++ = NEXT_CHAR;
681                     if (ch==(char)EOF) {
682                         abort_socket(); /* End of file, close socket */
683                         return;         /* End of file on response */
684                     }
685                     if ((ch == '\n')
686                         || (p == &line[LINE_LENGTH]) ) {
687                     
688                         *--p=0;         /* Terminate  & chop LF*/
689                         p = line;               /* Restart at beginning */
690                         if (TRACE) printf("G %s\n", line);
691                         switch(line[0]) {
692     
693                         case '.':
694                             done = (line[1]<' ');       /* End of article? */
695                             break;
696     
697                         case 'S':
698                         case 's':
699                             if (match(line, "SUBJECT:"))
700                                 strcpy(subject, line+9);/* Save subject */
701                             break;
702     
703                         case 'M':
704                         case 'm':
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);
709                             }
710                             break;
711     
712                         case 'f':
713                         case 'F':
714                             if (match(line, "FROM:")) {
715                                 char * p;
716                                 strcpy(author,
717                                         author_name(strchr(line,':')+1));
718                                 p = author + strlen(author) - 1;
719                                 if (*p=='\n') *p = 0;   /* Chop off newline */
720                             }
721                             break;
722                                     
723                         } /* end switch on first character */
724                     } /* if end of line */
725                 } /* Loop over characters */
726     
727                 sprintf(buffer, "\"%s\" - %s", subject, author);
728                 if (reference) {
729                     write_anchor(buffer, reference);
730                     free(reference);
731                     reference=0;
732                 } else {
733                     HText_appendText(HT, buffer);
734                 }
735                 HText_appendParagraph(HT);
736                 
737     
738 /*      Change the title bar to indicate progress!
739 */
740                 if (art%10 == 0) {
741                     sprintf(buffer, "Reading newsgroup %s,  Article %d (of %d-%d) ...",
742                             groupName, art, first, last);
743                     HTAnchor_setTitle(node_anchor, buffer);
744                 }
745     
746             } /* If good response */
747         } /* Loop over article */           
748     } /* If read headers */
749     
750 /*      Link to later articles
751 */
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");
762         HText_endAnchor(HT);
763         HText_appendText(HT,  "...)\n");
764     }
765     
766 /*      Set window title
767 */
768     sprintf(buffer, "Newsgroup %s,  Articles %d-%d",
769                 groupName, first_required, last_required);
770     HTAnchor_setTitle(node_anchor, buffer);
771
772 }
773
774
775 /*              Load by name                                    HTLoadNews
776 **              ============
777 */
778 PUBLIC int HTLoadNews ARGS3(
779         CONST char *,arg,
780         HTParentAnchor *,anAnchor,
781         int,diag)
782 {
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 */
790
791     diagnostic = diag;                  /* set global flag */
792     
793     if (TRACE) printf("HTNews: Looking for %s\n", arg);
794     get_styles();
795     
796     if (!initialized) initialized = initialize();
797     if (!initialized) return -1;        /* FAIL */
798     
799     {
800         char * p1;
801
802 /*      We will ask for the document, omitting the host name & anchor.
803 **
804 **      Syntax of address is
805 **              xxx@yyy                 Article
806 **              <xxx@yyy>               Same article
807 **              xxxxx                   News group (no "@")
808 */        
809         group_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')==0);
810         list_wanted  = (strchr(arg, '@')==0) && (strchr(arg, '*')!=0);
811         
812         p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
813         if (list_wanted) {
814             strcpy(command, "LIST ");
815         } else if (group_wanted) {
816             char * slash = strchr(p1, '/');
817             strcpy(command, "GROUP ");
818             first = 0;
819             last = 0;
820             if (slash) {
821                 *slash = 0;
822                 strcpy(groupName, p1);
823                 *slash = '/';
824                 (void) sscanf(slash+1, "%d-%d", &first, &last);
825             } else {
826                 strcpy(groupName, p1);
827             }
828             strcat(command, groupName);
829         } else {
830             strcpy(command, "ARTICLE ");
831             if (strchr(p1, '<')==0) strcat(command,"<");
832             strcat(command, p1);
833             if (strchr(p1, '>')==0) strcat(command,">");
834         }
835         free(p1);
836
837         strcat(command, "\r\n");                /* CR LF, as in rfc 977 */
838         
839     } /* scope of p1 */
840     
841     if (!*arg) return NO;                       /* Ignore if no name */
842
843     
844 /*      Make a hypertext object with an anchor list.
845 */       
846     node_anchor = anAnchor;
847     HT = HText_new(anAnchor);
848     HText_beginAppend(HT);
849         
850 /*      Now, let's get a stream setup up from the NewsHost:
851 */       
852     for(retries=0;retries<2; retries++){
853     
854         if (s<0) {
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));
859             if (status<0){
860                 char message[256];
861                 NETCLOSE(s);
862                 s = -1;
863                 if (TRACE) printf("HTNews: Unable to connect to news host.\n");
864 /*              if (retries<=1) continue;   WHY TRY AGAIN ?     */
865 #ifdef NeXTStep
866                 NXRunAlertPanel(NULL,
867                     "Could not access newshost %s.",
868                     NULL,NULL,NULL,
869                     NewsHost);
870 #else
871                 fprintf(stderr, "Could not access newshost %s\n",
872                     NewsHost);
873 #endif
874                 sprintf(message,
875 "\nCould not access %s.\n\n (Check default WorldWideWeb NewsHost ?)\n",
876                     NewsHost);
877                 HText_beginAppend(HT);
878                 HText_appendText(HT, message);
879                 HText_endAppend(HT);
880                 return YES;
881             } else {
882                 if (TRACE) printf("HTNews: Connected to news host %s.\n",
883                                 NewsHost);
884                 HTInitInput(s);         /* set up buffering */
885                 if ((response(NULL) / 100) !=2) {
886                         NETCLOSE(s);
887                         s = -1;
888 #ifdef NeXTStep
889                         NXRunAlertPanel("News access",
890                             "Could not retrieve information:\n   %s.",
891                             NULL,NULL,NULL,
892                             response_text);
893 #endif
894                         HTAnchor_setTitle(node_anchor, "News host response");
895                         HText_beginAppend(HT);
896                         HText_appendText(HT,
897                              "Sorry, could not retrieve information: ");
898                         HText_appendText(HT, response_text);
899                         HText_endAppend(HT);
900                         return YES;
901                 }
902             }
903         } /* If needed opening */
904         
905         HTAnchor_setTitle(node_anchor, arg);/* Tell user something's happening */
906         status = response(command);
907         if (status<0) break;
908         if ((status/ 100) !=2) {
909 /*          NXRunAlertPanel("News access", response_text,
910                 NULL,NULL,NULL);
911 */
912             HText_beginAppend(HT);
913             HText_appendText(HT, response_text);
914             HText_endAppend(HT);
915             NETCLOSE(s);
916             s = -1;
917 /* return HT; -- no:the message might be "Timeout-disconnected" left over */
918             continue;   /*      Try again */
919         }
920   
921 /*      Load a group, article, etc
922 */
923         HText_beginAppend(HT);
924         
925         if (list_wanted) read_list();
926         else if (group_wanted) read_group(groupName, first, last);
927         else read_article();
928
929         HText_endAppend(HT);
930         return YES;
931         
932     } /* Retry loop */
933     
934     HText_beginAppend(HT);
935     HText_appendText(HT, "Sorry, could not load requested news.\n");
936     HText_endAppend(HT);
937     
938 /*    NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
939             NULL,NULL,NULL, arg);No -- message earlier wil have covered it */
940
941     return YES;
942 }
943