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