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