Annotation of libwww/Library/src/HTNews.c, revision 1.1.1.1
1.1 timbl 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. Default 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 = DEFAULT_NEWS_HOST;
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 news host name `%s'.",
125: NULL, NULL, NULL, NewsHost);
126: #else
127: fprintf(stderr,
128: "HTNews: Can't find news host `%s'.\n",NewsHost);
129: fprintf(stderr,
130: " Please see online documentation for instructions to set the news host.\n");
131: #endif
132: CTRACE(tfp,
133: "HTNews: Can't find news host `%s'.\n",NewsHost);
134: return NO; /* Fail */
135: }
136: memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
137: }
138:
139: if (TRACE) fprintf(stderr,
140: "HTNews: Parsed address as port %4x, inet %d.%d.%d.%d\n",
141: (unsigned int)ntohs(sin->sin_port),
142: (int)*((unsigned char *)(&sin->sin_addr)+0),
143: (int)*((unsigned char *)(&sin->sin_addr)+1),
144: (int)*((unsigned char *)(&sin->sin_addr)+2),
145: (int)*((unsigned char *)(&sin->sin_addr)+3));
146:
147: s = -1; /* Disconnected */
148:
149: return YES;
150: }
151:
152:
153:
154: /* Get Styles from stylesheet
155: ** --------------------------
156: */
157: PRIVATE void get_styles NOARGS
158: {
159: if (!heading1Style) heading1Style = HTStyleNamed(styleSheet, "Heading1");
160: if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
161: if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
162: }
163:
164:
165: /* Send NNTP Command line to remote host & Check Response
166: ** ------------------------------------------------------
167: **
168: ** On entry,
169: ** command points to the command to be sent, including CRLF, or is null
170: ** pointer if no command to be sent.
171: ** On exit,
172: ** Negative status indicates transmission error, socket closed.
173: ** Positive status is an NNTP status.
174: */
175:
176:
177: PRIVATE int response ARGS1(CONST char *,command)
178: {
179: int result;
180: char * p = response_text;
181: if (command) {
182: int status;
183: int length = strlen(command);
184: if (TRACE) fprintf(stderr, "NNTP command to be sent: %s", command);
185: #ifdef NOT_ASCII
186: {
187: CONST char * p;
188: char * q;
189: char ascii[LINE_LENGTH+1];
190: for(p = command, q=ascii; *p; p++, q++) {
191: *q = TOASCII(*p);
192: }
193: status = NETWRITE(s, ascii, length);
194: }
195: #else
196: status = NETWRITE(s, command, length);
197: #endif
198: if (status<0){
199: if (TRACE) fprintf(stderr,
200: "HTNews: Unable to send command. Disconnecting.\n");
201: NETCLOSE(s);
202: s = -1;
203: return status;
204: } /* if bad status */
205: } /* if command to be sent */
206:
207: for(;;) {
208: if (((*p++=NEXT_CHAR) == '\n') || (p == &response_text[LINE_LENGTH])) {
209: *p++=0; /* Terminate the string */
210: if (TRACE) fprintf(stderr, "NNTP Response: %s\n", response_text);
211: sscanf(response_text, "%d", &result);
212: return result;
213: } /* if end of line */
214:
215: if (*(p-1) < 0) {
216: if (TRACE) fprintf(stderr,
217: "HTNews: EOF on read, closing socket %d\n", s);
218: NETCLOSE(s); /* End of file, close socket */
219: return s = -1; /* End of file on response */
220: }
221: } /* Loop over characters */
222: }
223:
224:
225: /* Case insensitive string comparisons
226: ** -----------------------------------
227: **
228: ** On entry,
229: ** template must be already un upper case.
230: ** unknown may be in upper or lower or mixed case to match.
231: */
232: PRIVATE BOOL match ARGS2 (CONST char *,unknown, CONST char *,template)
233: {
234: CONST char * u = unknown;
235: CONST char * t = template;
236: for (;*u && *t && (TOUPPER(*u)==*t); u++, t++) /* Find mismatch or end */ ;
237: return (BOOL)(*t==0); /* OK if end of template */
238: }
239:
240: /* Find Author's name in mail address
241: ** ----------------------------------
242: **
243: ** On exit,
244: ** THE EMAIL ADDRESS IS CORRUPTED
245: **
246: ** For example, returns "Tim Berners-Lee" if given any of
247: ** " Tim Berners-Lee <tim@online.cern.ch> "
248: ** or " tim@online.cern.ch ( Tim Berners-Lee ) "
249: */
250: PRIVATE char * author_name ARGS1 (char *,email)
251: {
252: char *s, *e;
253:
254: if ((s=strchr(email,'(')) && (e=strchr(email, ')')))
255: if (e>s) {
256: *e=0; /* Chop off everything after the ')' */
257: return HTStrip(s+1); /* Remove leading and trailing spaces */
258: }
259:
260: if ((s=strchr(email,'<')) && (e=strchr(email, '>')))
261: if (e>s) {
262: strcpy(s, e+1); /* Remove <...> */
263: return HTStrip(email); /* Remove leading and trailing spaces */
264: }
265:
266: return HTStrip(email); /* Default to the whole thing */
267:
268: }
269:
270:
271: /* Paste in an Anchor
272: ** ------------------
273: **
274: **
275: ** On entry,
276: ** HT has a selection of zero length at the end.
277: ** text points to the text to be put into the file, 0 terminated.
278: ** addr points to the hypertext refernce address,
279: ** terminated by white space, comma, NULL or '>'
280: */
281: PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
282: {
283: char href[LINE_LENGTH+1];
284:
285: {
286: CONST char * p;
287: strcpy(href,"news:");
288: for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
289: strncat(href, addr, p-addr); /* Make complete hypertext reference */
290: }
291:
292: HText_beginAnchor(HT,
293: HTAnchor_findChildAndLink(node_anchor, "", href, 0));
294: HText_appendText(HT, text);
295: HText_endAnchor(HT);
296: }
297:
298:
299: /* Write list of anchors
300: ** ---------------------
301: **
302: ** We take a pointer to a list of objects, and write out each,
303: ** generating an anchor for each.
304: **
305: ** On entry,
306: ** HT has a selection of zero length at the end.
307: ** text points to a comma or space separated list of addresses.
308: ** On exit,
309: ** *text is NOT any more chopped up into substrings.
310: */
311: PRIVATE void write_anchors ARGS1 (char *,text)
312: {
313: char * start = text;
314: char * end;
315: char c;
316: for (;;) {
317: for(;*start && (WHITE(*start)); start++); /* Find start */
318: if (!*start) return; /* (Done) */
319: for(end=start; *end && (*end!=' ') && (*end!=','); end++);/* Find end */
320: if (*end) end++; /* Include comma or space but not NULL */
321: c = *end;
322: *end = 0;
323: write_anchor(start, start);
324: *end = c;
325: start = end; /* Point to next one */
326: }
327: }
328:
329: /* Abort the connection abort_socket
330: ** --------------------
331: */
332: PRIVATE void abort_socket NOARGS
333: {
334: if (TRACE) fprintf(stderr,
335: "HTNews: EOF on read, closing socket %d\n", s);
336: NETCLOSE(s); /* End of file, close socket */
337: HText_appendText(HT, "Network Error: connection lost");
338: HText_appendParagraph(HT);
339: s = -1; /* End of file on response */
340: return;
341: }
342:
343: /* Read in an Article read_article
344: ** ------------------
345: **
346: **
347: ** Note the termination condition of a single dot on a line by itself.
348: ** RFC 977 specifies that the line "folding" of RFC850 is not used, so we
349: ** do not handle it here.
350: **
351: ** On entry,
352: ** s Global socket number is OK
353: ** HT Global hypertext object is ready for appending text
354: */
355: PRIVATE void read_article NOARGS
356: {
357:
358: char line[LINE_LENGTH+1];
359: char *references=NULL; /* Hrefs for other articles */
360: char *newsgroups=NULL; /* Newsgroups list */
361: char *p = line;
362: BOOL done = NO;
363:
364: /* Read in the HEADer of the article:
365: **
366: ** The header fields are either ignored, or formatted and put into the
367: ** Text.
368: */
369: if (!diagnostic) {
370: HText_setStyle(HT, addressStyle);
371: while(!done){
372: char ch = *p++ = NEXT_CHAR;
373: if (ch==(char)EOF) {
374: abort_socket(); /* End of file, close socket */
375: return; /* End of file on response */
376: }
377: if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
378: *--p=0; /* Terminate the string */
379: if (TRACE) fprintf(stderr, "H %s\n", line);
380:
381: if (line[0]=='.') {
382: if (line[1]<' ') { /* End of article? */
383: done = YES;
384: break;
385: }
386:
387: } else if (line[0]<' ') {
388: break; /* End of Header? */
389: } else if (match(line, "SUBJECT:")) {
390: HTAnchor_setTitle(node_anchor, line+8);
391: HText_setStyle(HT, heading1Style);
392: HText_appendText(HT, line+8);
393: HText_setStyle(HT, addressStyle);
394: } else if (match(line, "DATE:")
395: || match(line, "FROM:")
396: || match(line, "ORGANIZATION:")) {
397: strcat(line, "\n");
398: HText_appendText(HT, strchr(line,':')+1);
399: } else if (match(line, "NEWSGROUPS:")) {
400: StrAllocCopy(newsgroups, HTStrip(strchr(line,':')+1));
401:
402: } else if (match(line, "REFERENCES:")) {
403: StrAllocCopy(references, HTStrip(strchr(line,':')+1));
404:
405: } /* end if match */
406: p = line; /* Restart at beginning */
407: } /* if end of line */
408: } /* Loop over characters */
409:
410: HText_appendCharacter(HT, '\n');
411: HText_setStyle(HT, textStyle);
412: if (newsgroups) {
413: HText_appendText(HT, "\nNewsgroups: ");
414: write_anchors(newsgroups);
415: free(newsgroups);
416: }
417:
418: if (references) {
419: HText_appendText(HT, "\nReferences: ");
420: write_anchors(references);
421: free(references);
422: }
423:
424: HText_appendText(HT, "\n\n\n");
425:
426: } else { /* diagnostic */
427: HText_setStyle(HT, textStyle);
428: }
429:
430: /* Read in the BODY of the Article:
431: */
432: p = line;
433: while(!done){
434: char ch = *p++ = NEXT_CHAR;
435: if (ch==(char)EOF) {
436: abort_socket(); /* End of file, close socket */
437: return; /* End of file on response */
438: }
439: if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
440: *p++=0; /* Terminate the string */
441: if (TRACE) fprintf(stderr, "B %s", line);
442: if (line[0]=='.') {
443: if (line[1]<' ') { /* End of article? */
444: done = YES;
445: break;
446: } else { /* Line starts with dot */
447: HText_appendText(HT, &line[1]); /* Ignore first dot */
448: }
449: } else {
450:
451: /* Normal lines are scanned for buried references to other articles.
452: ** Unfortunately, it will pick up mail addresses as well!
453: */
454: char *l = line;
455: char * p;
456: while (p=strchr(l, '<')) {
457: char *q = strchr(p,'>');
458: char *at = strchr(p, '@');
459: if (q && at && at<q) {
460: char c = q[1];
461: q[1] = 0; /* chop up */
462: *p = 0;
463: HText_appendText(HT, l);
464: *p = '<'; /* again */
465: *q = 0;
466: HText_beginAnchor(HT,
467: HTAnchor_findChildAndLink(
468: node_anchor, "", p+1, 0));
469: *q = '>'; /* again */
470: HText_appendText(HT, p);
471: HText_endAnchor(HT);
472: q[1] = c; /* again */
473: l=q+1;
474: } else break; /* line has unmatched <> */
475: }
476: HText_appendText(HT, l); /* Last bit of the line */
477: } /* if not dot */
478: p = line; /* Restart at beginning */
479: } /* if end of line */
480: } /* Loop over characters */
481: }
482:
483:
484: /* Read in a List of Newsgroups
485: ** ----------------------------
486: */
487: /*
488: ** Note the termination condition of a single dot on a line by itself.
489: ** RFC 977 specifies that the line "folding" of RFC850 is not used, so we
490: ** do not handle it here.
491: */
492: PRIVATE void read_list NOARGS
493: {
494:
495: char line[LINE_LENGTH+1];
496: char *p;
497: BOOL done = NO;
498:
499: /* Read in the HEADer of the article:
500: **
501: ** The header fields are either ignored, or formatted and put into the
502: ** Text.
503: */
504: HText_setStyle(HT, heading1Style);
505: HText_appendText(HT, "Newsgroups");
506: HText_setStyle(HT, textStyle);
507: p = line;
508: while(!done){
509: char ch = *p++ = NEXT_CHAR;
510: if (ch==(char)EOF) {
511: abort_socket(); /* End of file, close socket */
512: return; /* End of file on response */
513: }
514: if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
515: *p++=0; /* Terminate the string */
516: if (TRACE) fprintf(stderr, "B %s", line);
517: if (line[0]=='.') {
518: if (line[1]<' ') { /* End of article? */
519: done = YES;
520: break;
521: } else { /* Line starts with dot */
522: HText_appendText(HT, &line[1]);
523: }
524: } else {
525:
526: /* Normal lines are scanned for references to newsgroups.
527: */
528: char group[LINE_LENGTH];
529: int first, last;
530: char postable;
531: if (sscanf(line, "%s %d %d %c", group, &first, &last, &postable)==4)
532: write_anchor(line, group);
533: else
534: HText_appendText(HT, line);
535: } /* if not dot */
536: p = line; /* Restart at beginning */
537: } /* if end of line */
538: } /* Loop over characters */
539: }
540:
541:
542: /* Read in a Newsgroup
543: ** -------------------
544: ** Unfortunately, we have to ask for each article one by one if we
545: ** want more than one field.
546: **
547: */
548: PRIVATE void read_group ARGS3(
549: CONST char *,groupName,
550: int,first_required,
551: int,last_required
552: )
553: {
554: char line[LINE_LENGTH+1];
555: char author[LINE_LENGTH+1];
556: char subject[LINE_LENGTH+1];
557: char *p;
558: BOOL done;
559:
560: char buffer[LINE_LENGTH];
561: char *reference=0; /* Href for article */
562: int art; /* Article number WITHIN GROUP */
563: int status, count, first, last; /* Response fields */
564: /* count is only an upper limit */
565:
566: sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
567: if(TRACE) printf("Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
568: status, count, first, last, first_required, last_required);
569: if (last==0) {
570: HText_appendText(HT, "\nNo articles in this group.\n");
571: return;
572: }
573:
574: #define FAST_THRESHOLD 100 /* Above this, read IDs fast */
575: #define CHOP_THRESHOLD 50 /* Above this, chop off the rest */
576:
577: if (first_required<first) first_required = first; /* clip */
578: if ((last_required==0) || (last_required > last)) last_required = last;
579:
580: if (last_required<=first_required) {
581: HText_appendText(HT, "\nNo articles in this range.\n");
582: return;
583: }
584:
585: if (last_required-first_required+1 > MAX_CHUNK) { /* Trim this block */
586: first_required = last_required-CHUNK_SIZE+1;
587: }
588: if (TRACE) printf (
589: " Chunk will be (%d-%d)\n", first_required, last_required);
590:
591: /* Link to earlier articles
592: */
593: if (first_required>first) {
594: int before; /* Start of one before */
595: if (first_required-MAX_CHUNK <= first) before = first;
596: else before = first_required-CHUNK_SIZE;
597: sprintf(buffer, "%s/%d-%d", groupName, before, first_required-1);
598: if (TRACE) fprintf(stderr, " Block before is %s\n", buffer);
599: HText_appendText(HT, " (");
600: HText_beginAnchor(HT,
601: HTAnchor_findChildAndLink(node_anchor, "", buffer, 0));
602: HText_appendText(HT, "Earlier articles");
603: HText_endAnchor(HT);
604: HText_appendText(HT, "...)\n");
605: }
606:
607: done = NO;
608:
609: /*#define USE_XHDR*/
610: #ifdef USE_XHDR
611: if (count>FAST_THRESHOLD) {
612: sprintf(buffer,
613: "\nThere are about %d articles currently available in %s, IDs as follows:\n\n",
614: count, groupName);
615: HText_appendText(HT, buffer);
616: sprintf(buffer, "XHDR Message-ID %d-%d\n", first, last);
617: status = response(buffer);
618: if (status==221) {
619:
620: p = line;
621: while(!done){
622: char ch = *p++ = NEXT_CHAR;
623: if (ch==(char)EOF) {
624: abort_socket(); /* End of file, close socket */
625: return; /* End of file on response */
626: }
627: if ((ch == '\n') || (p == &line[LINE_LENGTH])) {
628: *p++=0; /* Terminate the string */
629: if (TRACE) fprintf(stderr, "X %s", line);
630: if (line[0]=='.') {
631: if (line[1]<' ') { /* End of article? */
632: done = YES;
633: break;
634: } else { /* Line starts with dot */
635: /* Ignore strange line */
636: }
637: } else {
638:
639: /* Normal lines are scanned for references to articles.
640: */
641: char * space = strchr(line, ' ');
642: if (space++)
643: write_anchor(space, space);
644: } /* if not dot */
645: p = line; /* Restart at beginning */
646: } /* if end of line */
647: } /* Loop over characters */
648:
649: /* leaving loop with "done" set */
650: } /* Good status */
651: };
652: #endif
653:
654: /* Read newsgroup using individual fields:
655: */
656: if (!done) {
657: if (first==first_required && last==last_required)
658: HText_appendText(HT, "\nAll available articles in ");
659: else HText_appendText(HT, "\nArticles in ");
660: HText_appendText(HT, groupName);
661: HText_appendText(HT, "\n\n");
662: for(art=first_required; art<=last_required; art++) {
663:
664: /*#define OVERLAP*/
665: #ifdef OVERLAP
666: /* With this code we try to keep the server running flat out by queuing just
667: ** one extra command ahead of time. We assume (1) that the server won't abort
668: ** if it gets input during output, and (2) that TCP buffering is enough for the
669: ** two commands. Both these assumptions seem very reasonable. However, we HAVE
670: ** had a hangup with a loaded server.
671: */
672: if (art==first_required) {
673: if (art==last_required) {
674: sprintf(buffer, "HEAD %d\n", art); /* Only one */
675: status = response(buffer);
676: } else { /* First of many */
677: sprintf(buffer, "HEAD %d\nHEAD %d\n", art, art+1);
678: status = response(buffer);
679: }
680: } else if (art==last_required) { /* Last of many */
681: status = response(NULL);
682: } else { /* Middle of many */
683: sprintf(buffer, "HEAD %d\n", art+1);
684: status = response(buffer);
685: }
686:
687: #else /* NOT OVERLAP */
688: sprintf(buffer, "HEAD %d\n", art);
689: status = response(buffer);
690: #endif /* NOT OVERLAP */
691:
692: if (status == 221) { /* Head follows - parse it:*/
693:
694: p = line; /* Write pointer */
695: done = NO;
696: while(!done){
697: char ch = *p++ = NEXT_CHAR;
698: if (ch==(char)EOF) {
699: abort_socket(); /* End of file, close socket */
700: return; /* End of file on response */
701: }
702: if ((ch == '\n')
703: || (p == &line[LINE_LENGTH]) ) {
704:
705: *--p=0; /* Terminate & chop LF*/
706: p = line; /* Restart at beginning */
707: if (TRACE) fprintf(stderr, "G %s\n", line);
708: switch(line[0]) {
709:
710: case '.':
711: done = (line[1]<' '); /* End of article? */
712: break;
713:
714: case 'S':
715: case 's':
716: if (match(line, "SUBJECT:"))
717: strcpy(subject, line+9);/* Save subject */
718: break;
719:
720: case 'M':
721: case 'm':
722: if (match(line, "MESSAGE-ID:")) {
723: char * addr = HTStrip(line+11) +1; /* Chop < */
724: addr[strlen(addr)-1]=0; /* Chop > */
725: StrAllocCopy(reference, addr);
726: }
727: break;
728:
729: case 'f':
730: case 'F':
731: if (match(line, "FROM:")) {
732: char * p;
733: strcpy(author,
734: author_name(strchr(line,':')+1));
735: p = author + strlen(author) - 1;
736: if (*p=='\n') *p = 0; /* Chop off newline */
737: }
738: break;
739:
740: } /* end switch on first character */
741: } /* if end of line */
742: } /* Loop over characters */
743:
744: sprintf(buffer, "\"%s\" - %s", subject, author);
745: if (reference) {
746: write_anchor(buffer, reference);
747: free(reference);
748: reference=0;
749: } else {
750: HText_appendText(HT, buffer);
751: }
752: HText_appendParagraph(HT);
753:
754:
755: /* Change the title bar to indicate progress!
756: */
757: if (art%10 == 0) {
758: sprintf(buffer, "Reading newsgroup %s, Article %d (of %d-%d) ...",
759: groupName, art, first, last);
760: HTAnchor_setTitle(node_anchor, buffer);
761: }
762:
763: } /* If good response */
764: } /* Loop over article */
765: } /* If read headers */
766:
767: /* Link to later articles
768: */
769: if (last_required<last) {
770: int after; /* End of article after */
771: after = last_required+CHUNK_SIZE;
772: if (after==last) sprintf(buffer, "news:%s", groupName); /* original group */
773: else sprintf(buffer, "news:%s/%d-%d", groupName, last_required+1, after);
774: if (TRACE) fprintf(stderr, " Block after is %s\n", buffer);
775: HText_appendText(HT, "(");
776: HText_beginAnchor(HT, HTAnchor_findChildAndLink(
777: node_anchor, "", buffer, 0));
778: HText_appendText(HT, "Later articles");
779: HText_endAnchor(HT);
780: HText_appendText(HT, "...)\n");
781: }
782:
783: /* Set window title
784: */
785: sprintf(buffer, "Newsgroup %s, Articles %d-%d",
786: groupName, first_required, last_required);
787: HTAnchor_setTitle(node_anchor, buffer);
788:
789: }
790:
791:
792: /* Load by name HTLoadNews
793: ** ============
794: */
795: PUBLIC int HTLoadNews ARGS3(
796: CONST char *,arg,
797: HTParentAnchor *,anAnchor,
798: int,diag)
799: {
800: char command[257]; /* The whole command */
801: char groupName[GROUP_NAME_LENGTH]; /* Just the group name */
802: int status; /* tcp return */
803: int retries; /* A count of how hard we have tried */
804: BOOL group_wanted; /* Flag: group was asked for, not article */
805: BOOL list_wanted; /* Flag: group was asked for, not article */
806: int first, last; /* First and last articles asked for */
807:
808: diagnostic = diag; /* set global flag */
809:
810: if (TRACE) fprintf(stderr, "HTNews: Looking for %s\n", arg);
811: get_styles();
812:
813: if (!initialized) initialized = initialize();
814: if (!initialized) return -1; /* FAIL */
815:
816: {
817: CONST char * p1=arg;
818:
819: /* We will ask for the document, omitting the host name & anchor.
820: **
821: ** Syntax of address is
822: ** xxx@yyy Article
823: ** <xxx@yyy> Same article
824: ** xxxxx News group (no "@")
825: ** group/n1-n2 Articles n1 to n2 in group
826: */
827: group_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')==0);
828: list_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')!=0);
829:
830: /* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
831: /* Don't use HTParse because news: access doesn't follow traditional
832: rules. For instance, if the article reference contains a '#',
833: the rest of it is lost -- JFG 10/7/92, from a bug report */
834: if (!strncasecomp (arg, "news:", 5))
835: p1 = arg + 5; /* Skip "news:" prefix */
836: if (list_wanted) {
837: strcpy(command, "LIST ");
838: } else if (group_wanted) {
839: char * slash = strchr(p1, '/');
840: strcpy(command, "GROUP ");
841: first = 0;
842: last = 0;
843: if (slash) {
844: *slash = 0;
845: strcpy(groupName, p1);
846: *slash = '/';
847: (void) sscanf(slash+1, "%d-%d", &first, &last);
848: } else {
849: strcpy(groupName, p1);
850: }
851: strcat(command, groupName);
852: } else {
853: strcpy(command, "ARTICLE ");
854: if (strchr(p1, '<')==0) strcat(command,"<");
855: strcat(command, p1);
856: if (strchr(p1, '>')==0) strcat(command,">");
857: }
858: /* free(p1); * bug fix TBL 5 Aug 92 */
859:
860: strcat(command, "\r\n"); /* CR LF, as in rfc 977 */
861:
862: } /* scope of p1 */
863:
864: if (!*arg) return NO; /* Ignore if no name */
865:
866:
867: /* Make a hypertext object with an anchor list.
868: */
869: node_anchor = anAnchor;
870: HT = HText_new(anAnchor);
871: HText_beginAppend(HT);
872:
873: /* Now, let's get a stream setup up from the NewsHost:
874: */
875: for(retries=0;retries<2; retries++){
876:
877: if (s<0) {
878: HTAnchor_setTitle(node_anchor, "Connecting to NewsHost ...");/* Tell user */
879: NEWS_PROGRESS("Connecting to NewsHost ...");
880: s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
881: status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
882: if (status<0){
883: char message[256];
884: NETCLOSE(s);
885: s = -1;
886: if (TRACE) fprintf(stderr, "HTNews: Unable to connect to news host.\n");
887: /* if (retries<=1) continue; WHY TRY AGAIN ? */
888: #ifdef NeXTStep
889: NXRunAlertPanel(NULL,
890: "Could not access newshost %s.",
891: NULL,NULL,NULL,
892: NewsHost);
893: #else
894: fprintf(stderr, "Could not access newshost %s\n",
895: NewsHost);
896: #endif
897: sprintf(message,
898: "\nCould not access %s.\n\n (Check default WorldWideWeb NewsHost ?)\n",
899: NewsHost);
900: HText_beginAppend(HT);
901: HText_appendText(HT, message);
902: HText_endAppend(HT);
903: return YES;
904: } else {
905: if (TRACE) fprintf(stderr, "HTNews: Connected to news host %s.\n",
906: NewsHost);
907: HTInitInput(s); /* set up buffering */
908: if ((response(NULL) / 100) !=2) {
909: NETCLOSE(s);
910: s = -1;
911: #ifdef NeXTStep
912: NXRunAlertPanel("News access",
913: "Could not retrieve information:\n %s.",
914: NULL,NULL,NULL,
915: response_text);
916: #endif
917: HTAnchor_setTitle(node_anchor, "News host response");
918: HText_beginAppend(HT);
919: HText_appendText(HT,
920: "Sorry, could not retrieve information: ");
921: HText_appendText(HT, response_text);
922: HText_endAppend(HT);
923: return YES;
924: }
925: }
926: } /* If needed opening */
927:
928: HTAnchor_setTitle(node_anchor, arg);/* Tell user something's happening */
929: status = response(command);
930: if (status<0) break;
931: if ((status/ 100) !=2) {
932: /* NXRunAlertPanel("News access", response_text,
933: NULL,NULL,NULL);
934: */
935: HText_beginAppend(HT);
936: HText_appendText(HT, response_text);
937: HText_endAppend(HT);
938: NETCLOSE(s);
939: s = -1;
940: /* return HT; -- no:the message might be "Timeout-disconnected" left over */
941: continue; /* Try again */
942: }
943:
944: /* Load a group, article, etc
945: */
946: HText_beginAppend(HT);
947:
948: if (list_wanted) read_list();
949: else if (group_wanted) read_group(groupName, first, last);
950: else read_article();
951:
952: HText_endAppend(HT);
953: return YES;
954:
955: } /* Retry loop */
956:
957: HText_beginAppend(HT);
958: HText_appendText(HT, "Sorry, could not load requested news.\n");
959: HText_endAppend(HT);
960:
961: /* NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
962: NULL,NULL,NULL, arg);No -- message earlier wil have covered it */
963:
964: return YES;
965: }
966:
Webmaster