/*	WorldWideWeb - Wide Area Informaion Server Access	HTWAIS.c
**	==================================================
**
**	This module allows a WWW server or client to read data from a
**	remote WAIS server and provide that data to a WWW client in
**      hypertext form.  Source files, once retrieved, are stored and
*8	used to provide information about the index when that is acessed.
**
** Authors
**	BK	Brewster Kahle, Thinking Machines, <Brewster@think.com>
**	TBL	Tim Berners-Lee, CERN <timbl@info.cern.ch>
**
** History
**	   Sep 91	TBL adapted shell-ui.c (BK) with HTRetrieve.c from WWW.
**	   Feb 91	TBL Generated HTML cleaned up a bit (quotes, escaping)
**			    Refers to lists of sources. 
**	   Mar 93	TBL   Lib 2.0 compatible module made.	
**
** Bugs
**	Uses C stream i/o to read and write sockets, which won't work
**	on VMS TCP systems.
**
**	Should cache connections.
**
**	ANSI C only as written
**
** WAIS comments:
**
**	1.  Separate directories for different system's .o would help
**	2.  Document ids are rather long!
**
** WWW Address mapping convention:
**
**	/servername/database/type/length/document-id
**
**	/servername/database?word+word+word
*/
/* WIDE AREA INFORMATION SERVER SOFTWARE:
   No guarantees or restrictions.  See the readme file for the full standard
   disclaimer.

   Brewster@think.com
*/
#include "../config.h"
#ifdef HAVE_WAIS

#define BIG 10000

/*			From WAIS
**			---------
*/

#include "HTUtils.h"
#ifdef __DECC
#define _DECC_V4_SOURCE
#endif /* DEC C, GEC */
#include <ui.h>
#ifdef __DECC
#undef _DECC_V4_SOURCE
#endif /* DEC C, GEC */

#define MAX_MESSAGE_LEN 100000
#define CHARS_PER_PAGE 4096 /* number of chars retrieved in each request */


/*			FROM WWW
**			--------
*/
#define HEX_ESCAPE '%'

#include "HTParse.h"
#include "HTAccess.h"		/* We implement a protocol */
#include "HTML.h"		/* The object we will generate */
#include "HTFile.h"
#include "HTAlert.h"
 

#ifndef DISABLE_TRACE
extern int www2Trace;
#endif

PRIVATE char line[2048];	/* For building strings to display */
				/* Must be able to take id */

#include "HTFormat.h"
#include "HTTCP.h"

/*		Hypertext object building machinery
*/
#include "HTML.h"

#define PUTC(c) (*target->isa->put_character)(target, c)
#define PUTBLOCK(c, len) (*target->isa->put_block)(target, c, len)
#define PUTS(s) (*target->isa->put_string)(target, s)
#define START(e) (*target->isa->start_element)(target, e, 0, 0)
#define END(e) (*target->isa->end_element)(target, e)
#define END_TARGET (*target->isa->end_document)(target)
#define FREE_TARGET (*target->isa->free)(target)

struct _HTStructured {
	WWW_CONST HTStructuredClass *isa;
	/* ... */
};

struct _HTStream {
	WWW_CONST HTStreamClass *isa;
	/* ... */
};


/* Modified from Jonny G's version in ui/question.c */

PRIVATE void showDiags (HTStream *target, diagnosticRecord **d)
{
  long i;

  for (i = 0; d[i]; i++) {
    if (d[i]->ADDINFO) {
      PUTS("Diagnostic code is ");
      PUTS(d[i]->DIAG);
      PUTC(' ');
      PUTS(d[i]->ADDINFO);
      PUTC('\n'); ;
    }
  }
}

/*	Matrix of allowed characters in filenames
**	-----------------------------------------
*/

PRIVATE BOOL acceptable[256];
PRIVATE BOOL acceptable_inited = NO;

PRIVATE void init_acceptable (void)
{
    unsigned int i;
    char *good = 
          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";

    for (i = 0; i < 256; i++)
         acceptable[i] = NO;
    for (; *good; good++)
         acceptable[(unsigned int)*good] = YES;
    acceptable_inited = YES;
}

/*	Transform file identifier into WWW address
**	------------------------------------------
**
**
** On exit,
**	returns		nil if error
**			pointer to malloced string (must be freed) if ok
*/
PRIVATE char *WWW_from_archie (char *file)
{
    char *end, *result, *colon;

    for (end = file; *end > ' '; end++)		/* Assumes ASCII encoding */
        ;
    if (!(result = (char *)malloc(10 + (end - file))))
        return NULL;
    strcpy(result, "file://");
    strncat(result, file, end - file);
    colon = strchr(result + 7, ':');	/* Expect colon after host */
    if (colon) {
	for (; *colon; *colon = colon[1], colon++)	/* Move down */
	    ;
    }
    return result;
}

/*	Transform document identifier into URL
**	--------------------------------------
**
** Bugs: A static buffer of finite size is used!
**	The format of the docid MUST be good!
**
** On exit,
**	returns		nil if error
**			pointer to malloced string (must be freed) if ok
*/
PRIVATE char hex[17] = "0123456789ABCDEF";

PRIVATE char *WWW_from_WAIS (any *docid)
{
  static unsigned char buf[BIG];
  char num[10];
  unsigned char *q = buf;
  char *p;
  int i, l;

#ifndef DISABLE_TRACE
  if (www2Trace) {
      fprintf(stderr, "WAIS id (%d bytes) is ", (int)docid->size);
      for (p = docid->bytes; p < docid->bytes + docid->size; p++) {
          if ((*p >= ' ') && (*p<= '~')) {	/* Assume ASCII! */
              fprintf(stderr, "%c", *p);
          } else {
              fprintf(stderr, "<%x>", (unsigned)*p);
	  }
      }
      fprintf(stderr, "\n");
  }	 
#endif

  for (p = docid->bytes; (p < docid->bytes + docid->size) && (q < &buf[BIG]);) {
#ifndef DISABLE_TRACE
      if (www2Trace)
	  fprintf(stderr, "    Record type %d, length %d\n", p[0], p[1]);
#endif
      sprintf(num, "%d", (int)*p++);
      memcpy(q, num, strlen(num));
      q += strlen(num);
      *q++ = '=';		/* Separate */
      l = (int)((unsigned char)*p++);
      if (l > 127) {
          l = (l - 128) * 128;
          l += (int)((unsigned char)*p++);
      }
      for (i = 0; i < l; i++, p++) {
          if (!acceptable[(unsigned char)*p]) {
              *q++ = HEX_ESCAPE;
              *q++ = hex[((unsigned char)*p) >> 4];
              *q++ = hex[((unsigned char)*p) & 15];
          } else {
	      *q++ = (unsigned char)*p;
	  }
      }
      *q++ = ';';		/* Terminate field */
  }
  *q++ = '\0';			/* Terminate string */
#ifndef DISABLE_TRACE
  if (www2Trace) 
      fprintf(stderr, "WWW form of id: %s\n", buf); 
#endif

  {
    char *result = (char *)malloc(strlen((char *)buf) + 1);

    strcpy(result, (char *)buf);
    return result;
  }
}


/*	Transform URL into WAIS document identifier
**	-------------------------------------------
**
** On entry,
**	docname		points to valid name produced originally by
**			WWW_from_WAIS
** On exit,
**	docid->size	is valid
**	docid->bytes	is malloced and must later be freed.
*/
PRIVATE any *WAIS_from_WWW (any *docid, char *docname)
{
  char *z; 	/* Output pointer */
  char *sor;	/* Start of record - points to size field. */
  char *p; 	/* Input pointer */
  char *q; 	/* Poisition of "=" */
  char *s; 	/* Position of semicolon */
  int n;	/* size */

#ifndef DISABLE_TRACE
  if (www2Trace) 
      fprintf(stderr, "WWW id (to become WAIS id): %s\n", docname); 
#endif
  for (n = 0, p = docname; *p; p++) {	/* Count sizes of strings */
      n++;
      if (*p == ';') {
	  n--;		/* Not converted */
      } else if (*p == HEX_ESCAPE) {
	  n -= 2;	/* Save two bytes */
      }
      docid->size = n;
  }
  
  docid->bytes = (char *) malloc(docid->size + 32);  /* Result record */
  z = docid->bytes;
  
  for (p = docname; *p; ) {
      q = strchr(p, '=');
      if (!q) 
          return NULL;
      *q = '\0';
      *z++ = atoi(p);
      *q = '=';
      s = strchr(q, ';');	/* (Check only) */
      if (!s) 
          return NULL;	/* Bad!  No ';' */
      sor = z;          /* Remember where the size field was */
      z++;              /* Skip record size for now */
      
      {
        int len, tmp;

	for (p = q + 1; *p != ';' ;) {
            if (*p == HEX_ESCAPE) {
                char c;
                unsigned int b;

		p++;
	        c = *p++;
		b = from_hex(c);
		c = *p++;
		if (!c) 
                    break;	/* Odd number of chars! */
		*z++ = (b << 4) + from_hex(c);
            } else {
	        *z++ = *p++;	/* Record */
            }
        }
        len = z - sor - 1;
        z = sor;
        if (len > 127) {
            tmp = len / 128;
            len -= tmp * 128;
            tmp += 128;
            *z++ = (char)tmp;
            *z = (char)len;
        } else {
            *z = (char)len;
        }
        z++;
      }
      for (p = q + 1; *p != ';' ;) {
          if (*p == HEX_ESCAPE) {
              char c;
              unsigned int b;

	      p++;
              c = *p++;
              b = from_hex(c);
              c = *p++;
              if (!c) 
                  break;	/* Odd number of chars! */
              *z++ = (b << 4) + from_hex(c);
	  } else {
              *z++ = *p++;	/* Record */
          }
      }
      p++;			/* After semicolon: start of next record */
  }
  
#ifndef DISABLE_TRACE
  if (www2Trace) {
      fprintf(stderr, "WAIS id (%d bytes) is ", (int)docid->size);
      for (p = docid->bytes; p < docid->bytes + docid->size; p++) {
          if ((*p >= ' ') && (*p <= '~')) {
              fprintf(stderr, "%c", *p);
          } else {
              fprintf(stderr, "<%x>", (unsigned)*p);
          }
      }
      fprintf(stderr, "\n");
  }	 
#endif
  return docid;		/* Ok */
}


/*	Send a plain text record to the client		output_text_record()
**	--------------------------------------
*/

PRIVATE void output_text_record (HTStream *target,
				 WAISDocumentText *record,
				 boolean quote_string_quotes)
{
  if (record->DocumentText->size) {
      /* This cast should be unnecessary, as put_block should operate
       * on unsigned char from the start.  What was he thinking??? */
      PUTBLOCK((char *)record->DocumentText->bytes,
               record->DocumentText->size);
  }    
}


/*	Format A Search response for the client		display_search_response
**	---------------------------------------
*/
/* Modified from tracy shen's version in wutil.c
 * Displays either a text record or a set of headlines.
 */
PRIVATE void display_search_response (HTStructured *target,
				      SearchResponseAPDU *response,
				      char *database,
				      char *keywords)
{
  WAISSearchResponse *info;
  long i, k;
  BOOL archie;

  if (!response) {
      PUTS("Arrrrrrrrrrgh!");
      return;
  }
  archie = strstr(database, "archie") != 0;	/* Special handling */
  
#ifndef DISABLE_TRACE
  if (www2Trace) {
      fprintf(stderr, "HTWAIS: Displaying search response\n");
      fprintf(stderr, "  database 0x%08x '%s', response 0x%08x\n",
              database, database, response);
      fprintf(stderr, "  keywords 0x%08x '%s'\n", keywords, keywords);
  }
#endif
  sprintf(line,
  	  "Index %s contains the following %d item%s relevant to '%s'.\n",
	  database, (int)(response->NumberOfRecordsReturned),
	  response->NumberOfRecordsReturned == 1 ? "" : "s",
	  keywords);
  PUTS(line);
  PUTS("The first figure for each entry is its relative score, ");
  PUTS("the second the number of lines in the item.");
  START(HTML_MENU);

  if (response->DatabaseDiagnosticRecords) {
    info = (WAISSearchResponse *)response->DatabaseDiagnosticRecords;
    i = 0; 

    if (info->Diagnostics)
      showDiags((HTStream *)target, info->Diagnostics);

    if (info->DocHeaders) {
      for (k = 0; info->DocHeaders[k]; k++) {
	WAISDocumentHeader *head = info->DocHeaders[k];
	char *headline = trim_junk(head->Headline);
	any *docid = head->DocumentID;
	char *docname;			/* Printable version of docid */

	i++;
	/* Make a printable string out of the document id.
	 */
#ifndef DISABLE_TRACE
	if (www2Trace)
	  fprintf(stderr, "HTWAIS:  %2ld: Score: %4ld, lines:%4ld '%s'\n", i,
	          (long int)(info->DocHeaders[k]->Score),
	          (long int)(info->DocHeaders[k]->Lines), headline);
#endif
	START(HTML_LI);
	sprintf(line, "%4ld  %4ld  ", head->Score, head->Lines);
	PUTS(line);

	if (archie) {
	    char *www_name = WWW_from_archie(headline);

	    if (www_name) {
                PUTS("<A HREF=\"");
                PUTS(www_name);
                PUTS("\">");
		PUTS(headline);
                PUTS("</A>");
		free(www_name);
	    } else {
		PUTS(headline);
		PUTS(" (bad file name)");
	    }
	} else {  /* Not archie */
	    docname = WWW_from_WAIS(docid);
#ifndef DISABLE_TRACE
            if (www2Trace)
               fprintf(stderr, "HTWAIS: docname '%s'\n", docname);
#endif
	    if (docname) {
		char *dbname = HTEscape(database);
                char types_array[1000];  /* Bad */
                char *type_escaped;

                types_array[0] = '\0';

                if (head->Types) {
                    int i;

                    for (i = 0; head->Types[i]; i++) {
                        if (i)
                            strcat(types_array, ",");
                        type_escaped = HTEscape(head->Types[i]);
                        strcat(types_array, type_escaped);
                        free(type_escaped);
                    }
#ifndef DISABLE_TRACE
                    if (www2Trace)
                       fprintf(stderr, "Built types_array '%s'\n", types_array);
#endif
                } else {
                    strcat(types_array, "TEXT");
                }
		sprintf(line, "%s/%s/%d/%s", dbname, types_array,
                        (int)(head->DocumentLength), docname);

                PUTS("<A HREF=\"");
                if (head->Types && head->Types[0] && 
                    !strcmp(head->Types[0], "URL")) {
                    /* The real URL, maybe? */
#ifndef DISABLE_TRACE
                    if (www2Trace)
                        fprintf(stderr, "HTWAIS: Using headline '%s' as URL\n",
                                headline);
#endif
                    PUTS(headline);
                } else {
                    /* Our manufactured URL. */
                    PUTS(line);
                }
                PUTS("\">");
		PUTS(headline);
                PUTS("</A>");
		free(dbname);
		free(docname);
	    } else {
		 PUTS("(bad doc id)");
	    }
	}
      }  /* Next document header */
    }  /* If there were any document headers */
    
    if (info->ShortHeaders) {
      k = 0;
      while (info->ShortHeaders[k]) {
	i++;
	PUTS("(Short Header record, can't display)");
      }
    }
    if (info->LongHeaders) {
      k = 0;
      while (info->LongHeaders[k]) {
	i++;
	PUTS("\nLong Header record, can't display\n");
      }
    }
    if (info->Text) {
      k = 0;
      while (info->Text[k]) {
	i++;
	PUTS( "\nText record\n");
	output_text_record((HTStream*)target, info->Text[k++], false);
      }
    }
    if (info->Headlines) {
      k = 0;
      while (info->Headlines[k]) {
	i++;
	PUTS("\nHeadline record, can't display\n");
	/* dsply_headline_record(info->Headlines[k++]); */
      }
    }
    if (info->Codes) {
      k = 0;
      while (info->Codes[k]) {
	i++;
	PUTS("\nCode record, can't display\n");
	/* dsply_code_record(info->Codes[k++]); */
      }
    }
  }				/* Loop: display user info */
  END(HTML_MENU);
}


/* ------------------------------------------------------------------------ */
/* ---------------- Local copy of connect_to_server calls ----------------- */
/* ------------------------------------------------------------------------ */

/* Returns 1 on success, 0 on fail, -1 on interrupt. */
static int fd_mosaic_connect_to_server(char *host_name, long port, long *fd)
{
  /* New version. */
  char dummy[256];
  int status;

  sprintf(dummy, "wais://%s:%d/", host_name, port);

  status = HTDoConnect(dummy, "WAIS", 210, (int *)fd);
  if (status == HT_INTERRUPTED) {
#ifndef DISABLE_TRACE
      if (www2Trace)
          fprintf(stderr, "===WAIS=== interrupted in connect\n");
#endif
      HTProgress("Connection interrupted.");
      return -1;
  }
  if (status < 0)
      return 0;

  return 1;
}

/* Returns 1 on success, 0 on fail, -1 on interrupt. */
#ifndef VMS
static int mosaic_connect_to_server(char *host_name, long port, FILE **fp)
#else
static int mosaic_connect_to_server(char *host_name, long port, long *fdp)
#endif /* VMS, BSN */
{
#ifndef VMS
  FILE* file;
#endif /* VMS *, BSN */
  long fd;
  int rv;
  
  rv = fd_mosaic_connect_to_server(host_name, port, &fd);
  if (rv == 0) {
      HTProgress ("Could not connect to WAIS server.");
      return 0;
  } else if (rv == -1) {
      HTProgress("Connection interrupted.");
      return -1;
  }

#ifndef VMS
  if (!(file = fdopen(fd, "r+"))) {
      HTProgress("Could not open WAIS connection for reading.");
      return 0;
  }
  *fp = file;
#else
  *fdp = fd;
#endif /* VMS, BSN */
  return 1;
}


/* ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------ */
    
/* Pulled in from gui.c; corresponds to Rdata.max_wais_responses. */
extern int max_wais_responses;

#define MAX_KEYWORDS_LENGTH 5000
#define MAXDOCS max_wais_responses

/*		Load by name					HTLoadWAIS
**		============
**
**	This renders any object or search as required
*/
PUBLIC int HTLoadWAIS (WWW_CONST char *arg,
		       HTParentAnchor *anAnchor,
		       HTFormat format_out,
		       HTStream *sink)
{
  char *key;			/* Pointer to keywords in URL */
  char *request_message; 	/* Arbitrary message limit */
  char *response_message;	/* Arbitrary message limit */
  long request_buffer_length;	/* How of the request is left */
  SearchResponseAPDU  *retrieval_response = 0;
  char keywords[MAX_KEYWORDS_LENGTH + 1];
  char *server_name;	
  char *wais_database = NULL;		/* name of current database */
  char *www_database;			/* Same name escaped */
  char *service, *doctype, *doclength, *docname;
  long document_length;
#ifndef VMS
  FILE *connection = NULL;
#else
  int connection = 0;
#endif /* VMS, BSN */
  char *names;		/* Copy of arg to be hacked up */
  BOOL ok = NO;
  WAISSearchResponse *response;
  diagnosticRecord **diag;
  
  if (!acceptable_inited) 
      init_acceptable();
  
  /*	Decipher and check syntax of WWW address:
   **	----------------------------------------
   **
   **	First we remove the "wais:" if it was spcified.  920110
   */  
  names = HTParse(arg, "", PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
  key = strchr(names, '?');
  
  if (key) {
      char *p;

      *key++ = '\0';	/* Split off keywords */
      for (p = key; *p; p++) {
	  if (*p == '+')
	      *p = ' ';
      }
      HTUnEscape(key);
  }
  if (*names == '/') {
      server_name = names + 1;
      if (*server_name == '/')
          server_name++;	/* Accept one or two */
      www_database = strchr(server_name, '/');
      if (www_database) {
          *www_database++ = '\0';		/* Separate database name */
          doctype = strchr(www_database, '/');
          if (key) {
              ok = YES;	          /* Don't need doc details */
          } else if (doctype) {   /* If not search parse doc details */
              *doctype++ = '\0';  /* Separate rest of doc address */
              doclength = strchr(doctype, '/');
              if (doclength) {
                  *doclength++ = '\0';
                  
                  /* OK, now doclength should be the rest of the URL, right? */
#ifndef DISABLE_TRACE
                  if (www2Trace)
                      fprintf(stderr, 
                              "WAIS: doctype '%s', doclength\n~~~~\n%s\n~~~~\n",
                              doctype, doclength);
#endif
                  /* Multitype! */
                  if (strchr(doctype, ',')) {
                      HTStructured *target = 
                        		   HTML_new(anAnchor, format_out, sink);
                      char *t, *oldt;
                      int first;

#ifndef DISABLE_TRACE
                      if (www2Trace) {
                          fprintf(stderr, "WAIS: Hey boss, got multitype.\n");
                          fprintf(stderr, "WAIS: names is '%s'\n", names);
                      }
#endif
                      START(HTML_TITLE);
                      PUTS("Multiple Format Opportunity");
                      END(HTML_TITLE);
                      
                      START(HTML_H1);
                      PUTS("Multiple Format Opportunity");
                      END(HTML_H1);

                      PUTS("This is a multiformat WAIS response.  You may pick the format of your choice from the list that follows: <p>\n\n<ul>\n");

                      /* Get the first doctype. */
                      t = strtok(doctype, ",");

                      /* oldt is a copy of the first doctype,
                       * with leading period. */
                      oldt = (char *)malloc(strlen(t) + 16);
                      sprintf(oldt, ".%s", t);
                      
                      first = 1;
                      while (t && *t) {
                          /* Got a type, as t. */
                          PUTS("<li> <a href=\"wais:");
                          PUTS(names);
                          PUTS("/");
                          PUTS(www_database);
                          PUTS("/");
                          PUTS(t);
                          PUTS("/");
                          PUTS(doclength);
                          PUTS("\">");
                          
                          /* Unescape t in place; we don't need it anymore
                           * after this anyway. */
                          HTUnEscape(t);
                          PUTS(t);
                          PUTS("</a>\n");
                          t = strtok(NULL, ",");
                      }
                      free (oldt);
                      
                      PUTS("</ul>");
                      END_TARGET;
                      FREE_TARGET;
                      
                      /* Hey, WE'RE DONE! */
                      free(names);
                      return HT_LOADED;
                  }
                  document_length = atol(doclength);
                  if (document_length) {
                      docname = strchr(doclength, '/');
                      if (docname) {
                          *docname++ = '\0';
                          ok = YES;	/* To avoid a goto! */
                      }
                  }
              }  /* If doclength */
          } else {  /* No doctype?  Assume index required */
              if (!key)
		  key = "";
              ok = YES;
          }  /* If doctype */
      }  /* If database */
  }
  
  if (!ok) {
      free(names);
      return HT_NOT_LOADED;
  }

#ifndef DISABLE_TRACE
  if (www2Trace) 
      fprintf(stderr, "HTWAIS: Parsed OK; type is '%s'\n", doctype);
#endif

  service = strchr(names, ':');
  if (service) {
      *service++ = '\0';
  } else {
      service = "210";
  }
  
  if (!*server_name) {
#ifndef VMS
      connection = NULL;
#else
      connection = 0;
#endif
  } else if (!(key && !*key)) {
      int status;

#ifndef DISABLE_TRACE
      if (www2Trace)
        fprintf(stderr, "===WAIS=== calling mosaic_connect_to_server\n");
#endif
      status = mosaic_connect_to_server
#ifndef VMS
        (server_name, atoi(service), &connection);
#else
        (server_name, atoi(service), (long *)&connection);
#endif
      if (!status) {
#ifndef DISABLE_TRACE
          if (www2Trace)
              fprintf(stderr, "===WAIS=== connection failed\n");
#endif
          free(names);
          return HT_NOT_LOADED;
      } else if (status == -1) {
#ifndef DISABLE_TRACE
          if (www2Trace)
              fprintf(stderr, "===WAIS=== connection interrupted\n");
#endif
          free(names);
          return HT_INTERRUPTED;
      }
  }

  StrAllocCopy(wais_database, www_database);
  HTUnEscape(wais_database);
  
  /* This below fixed size stuff is terrible */
  request_message = (char *)s_malloc((size_t)MAX_MESSAGE_LEN * sizeof(char));
  response_message = (char *)s_malloc((size_t)MAX_MESSAGE_LEN * sizeof(char));
  
  /*	If keyword search is performed but there are no keywords,
   **	the user has followed a link to the index itself.  It would be
   **	appropriate at this point to send him the .SRC file - how?
   */
  if (key && !*key) {			/* I N D E X */
      HTStructured *target = HTML_new(anAnchor, format_out, sink);
      
      START(HTML_TITLE);
      PUTS(wais_database);
      PUTS(" index");
      END(HTML_TITLE);
      
      START(HTML_H1);
      PUTS(wais_database);
      END(HTML_H1);

      START(HTML_ISINDEX);
      START(HTML_P);
      
      END_TARGET;
      if (connection) 
          FW_close_connection(connection);
      FREE_TARGET;
  } else if (key) {			/* S E A R C H */
      char *p;
      HTStructured *target;
      
      strncpy(keywords, key, MAX_KEYWORDS_LENGTH);
      while (p = strchr(keywords, '+')) 
          *p = ' ';
      
      /* Send advance title to get something fast to the other end */

      target = HTML_new(anAnchor, format_out, sink);
      
      START(HTML_TITLE);
      PUTS(keywords);
      PUTS(" (in ");
      PUTS(wais_database);
      PUTS(")");
      END(HTML_TITLE);
      
      START(HTML_H1);
      PUTS(keywords);
      END(HTML_H1);
      START(HTML_ISINDEX);
      
      request_buffer_length = MAX_MESSAGE_LEN;  /* Amount left */
#ifndef DISABLE_TRACE
      if (www2Trace)
	  fprintf(stderr, "HTWAIS: Search for `%s' in `%s'\n",
                  keywords, wais_database);
#endif
      if (NULL == generate_search_apdu(request_message + HEADER_LENGTH, 
                                       &request_buffer_length, 
                                       keywords, wais_database, NULL, MAXDOCS))
          HTProgress("WAIS request too large; something went wrong.");
      
      if (!interpret_message(request_message, 
                             MAX_MESSAGE_LEN - request_buffer_length, 
                             response_message, MAX_MESSAGE_LEN, connection,
                             false  /* true verbose */ )) {
          HTProgress("WAIS returned message too large; something went wrong.");
      } else {		/* Returned message ok */
          SearchResponseAPDU *query_response = 0;

          readSearchResponseAPDU(&query_response,
                                 response_message + HEADER_LENGTH);
          /* We do want this to be called if !query_response, to
           * get our cute error message. */
          display_search_response(target, query_response,
				  wais_database, keywords);
          if (query_response) {
              if (query_response->DatabaseDiagnosticRecords)
                  freeWAISSearchResponse(
				     query_response->DatabaseDiagnosticRecords);
              freeSearchResponseAPDU(query_response);
          }
      }
      END_TARGET;
      if (connection) 
          FW_close_connection(connection);
      FREE_TARGET;
  } else {			/* D O C U M E N T    F E T C H */
      HTFormat format_in;
      HTStream *target;
      long count;
      any doc_chunk;
      any *docid = &doc_chunk;

      if (doctype)
          HTUnEscape(doctype);

#ifndef DISABLE_TRACE
      if (www2Trace)
          fprintf(stderr,
                  "===WAIS=== Retrieve document id `%s' type `%s' length %ld\n",
                  docname, doctype, document_length);
#endif      
      {
        char *tmp_doctype = strdup(doctype);
        char *tmp;

        for (tmp = tmp_doctype; *tmp; tmp++)
            *tmp = TOLOWER(*tmp);
#ifndef DISABLE_TRACE
        if (www2Trace)
            fprintf(stderr,
                    "===WAIS=== Doing HTAtom_exists on '%s'\n", tmp_doctype);
#endif
        format_in = HTAtom_exists(tmp_doctype);
        free(tmp_doctype);
      }
      if (!format_in) {
          char dummy[256];
          HTAtom *pencoding;
          int compressed;
          
          sprintf(dummy, "foo.%s", doctype);
          format_in = HTFileFormat(dummy, &pencoding,
                                   WWW_PLAINTEXT, &compressed);
          /* Assume it will always be at *least* WWW_PLAINTEXT. */
      }

      target = HTStreamStack(format_in, format_out, 0, sink, anAnchor);
      if (!target) {
          free(names);
          if (connection) 
              FW_close_connection(connection);
          return HT_NOT_LOADED;
      }

      /*	Decode hex or literal format for document ID
       */	
      WAIS_from_WWW(docid, docname);
      
      /*	Loop over slices of the document
       */	
      {
        int bytes = 0;
	int intr;
        char line[256];
        
        HTClearActiveIcon();
        count = 0;
        while (1) {
            char *type = s_strdup(doctype);	/* Gets freed I guess */

            request_buffer_length = MAX_MESSAGE_LEN;  /* Amount left */
#ifndef DISABLE_TRACE
            if (www2Trace) 
                fprintf(stderr, "HTWAIS: Slice number %ld\n", count);
#endif
            intr = HTCheckActiveIcon(1);
            if (intr) {
                HTProgress("Data transfer interrupted.");
                (*target->isa->handle_interrupt)(target);
                free(names);
                if (connection) 
                    FW_close_connection(connection);
		FREE_TARGET;
                return HT_INTERRUPTED;
            }
            if (!generate_retrieval_apdu(request_message + HEADER_LENGTH,
                &request_buffer_length, docid, CT_byte,
                count * CHARS_PER_PAGE, (count + 1) * CHARS_PER_PAGE,
                type, wais_database))
                HTProgress("WAIS error - retrieval may be unsuccessful.");

            free(type);
            
            /*	Actually do the transaction given by request_message */   
            if (!interpret_message(request_message, 
                MAX_MESSAGE_LEN - request_buffer_length, 
                response_message, MAX_MESSAGE_LEN, connection,
                false  /* true verbose */ )) {
                HTProgress("WAIS error - retrieval may be unsuccessful.");
                goto no_more_data;
            }
            
            /* 	Parse the result which came back into memory.
             */
            readSearchResponseAPDU(&retrieval_response, 
                                   response_message + HEADER_LENGTH);
            response = (WAISSearchResponse *)
				  retrieval_response->DatabaseDiagnosticRecords;
            diag = response->Diagnostics;

            if (!response->Text) {
#ifndef DISABLE_TRACE
                if (www2Trace)
                    fprintf(stderr,
			    "WAIS: no more data (NULL response->Text)\n");
#endif
                if (retrieval_response->DatabaseDiagnosticRecords)
                    freeWAISSearchResponse(
                        	 retrieval_response->DatabaseDiagnosticRecords);
                freeSearchResponseAPDU(retrieval_response);
                goto no_more_data;
            } else if (((WAISSearchResponse *)
                retrieval_response->DatabaseDiagnosticRecords)->Text[0]->DocumentText->size)
              {
                output_text_record(target,
                       ((WAISSearchResponse *)
                        retrieval_response->DatabaseDiagnosticRecords)->Text[0],
                       false);
            } else {  /* If text existed */
#ifndef DISABLE_TRACE
                if (www2Trace)
                    fprintf(stderr, "WAIS: no more data (fell through)\n");
#endif
                if (retrieval_response->DatabaseDiagnosticRecords)
                    freeWAISSearchResponse(
                        	 retrieval_response->DatabaseDiagnosticRecords);
                freeSearchResponseAPDU(retrieval_response);
                goto no_more_data;
            }

            /* Slightly inaccurate for last slice. */
            bytes += CHARS_PER_PAGE;
            sprintf(line, "Read %d bytes of data.", bytes);
            HTProgress(line);

            if (diag && diag[0] && diag[0]->ADDINFO &&
                !strcmp(diag[0]->DIAG, D_PresentRequestOutOfRange)) {
#ifndef DISABLE_TRACE
                if (www2Trace)
                    fprintf(stderr, "WAIS: no more data (diag)\n");
#endif
                if (retrieval_response->DatabaseDiagnosticRecords)
                    freeWAISSearchResponse(
                        	 retrieval_response->DatabaseDiagnosticRecords);
                freeSearchResponseAPDU(retrieval_response);
                goto no_more_data;
            }
            if (retrieval_response->DatabaseDiagnosticRecords)
                freeWAISSearchResponse(
                    		 retrieval_response->DatabaseDiagnosticRecords);
            freeSearchResponseAPDU(retrieval_response);

            count++;
        }  /* Loop over slices */
      }  /* Local variables */

    no_more_data:
      
      END_TARGET;
      /* Close the connection BEFORE calling system(), which can
       * happen in the free method. */
      if (connection) 
          FW_close_connection(connection);
      FREE_TARGET;
      
      free(docid->bytes);
  }  /* If document rather than search */

  if (wais_database) 
      free(wais_database);
  s_free(request_message);
  s_free(response_message);
  
  free(names);
  return HT_LOADED;
}

PUBLIC HTProtocol HTWAIS = { "wais", HTLoadWAIS, NULL };

#endif  /* HAVE_WAIS */
