home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / lynx-2.4 / WWW / Library / Implementation / HTNews.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-06-28  |  30.7 KB  |  1,211 lines

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