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