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