summaryrefslogtreecommitdiffstats
path: root/WWW/Library/Implementation/HTGopher.c
diff options
context:
space:
mode:
Diffstat (limited to 'WWW/Library/Implementation/HTGopher.c')
-rw-r--r--WWW/Library/Implementation/HTGopher.c2071
1 files changed, 2071 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTGopher.c b/WWW/Library/Implementation/HTGopher.c
new file mode 100644
index 0000000..0314179
--- /dev/null
+++ b/WWW/Library/Implementation/HTGopher.c
@@ -0,0 +1,2071 @@
+/*
+ * $LynxId: HTGopher.c,v 1.77 2022/04/01 00:18:09 tom Exp $
+ *
+ * GOPHER ACCESS HTGopher.c
+ * =============
+ *
+ * History:
+ * 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
+ * 29 Nov 91 Downgraded to C, for portable implementation.
+ * 10 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Added a
+ * form-based CSO/PH gateway. Can be invoked via a
+ * "cso://host[:port]/" or "gopher://host:105/2"
+ * URL. If a gopher URL is used with a query token
+ * ('?'), the old ISINDEX procedure will be used
+ * instead of the form-based gateway.
+ * 15 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Pass
+ * port 79, gtype 0 gopher URLs to the finger
+ * gateway.
+ */
+
+#define HTSTREAM_INTERNAL 1
+
+#include <HTUtils.h> /* Coding convention macros */
+#include <HTFile.h> /* For HTFileFormat() */
+
+#ifndef DISABLE_GOPHER
+#include <HTAlert.h>
+#include <HTParse.h>
+#include <HTTCP.h>
+#include <HTFinger.h>
+#include <LYGlobalDefs.h>
+
+/*
+ * Implements.
+ */
+#include <HTGopher.h>
+
+#define GOPHER_PORT 70 /* See protocol spec */
+#define CSO_PORT 105 /* See protocol spec */
+#define BIG 1024 /* Bug */
+#define LINE_LENGTH 256 /* Bug */
+
+/*
+ * Gopher entity types.
+ */
+#define GOPHER_TEXT '0'
+#define GOPHER_MENU '1'
+#define GOPHER_CSO '2'
+#define GOPHER_ERROR '3'
+#define GOPHER_MACBINHEX '4'
+#define GOPHER_PCBINARY '5'
+#define GOPHER_UUENCODED '6'
+#define GOPHER_INDEX '7'
+#define GOPHER_TELNET '8'
+#define GOPHER_BINARY '9'
+#define GOPHER_GIF 'g'
+#define GOPHER_HTML 'h' /* HTML */
+#define GOPHER_CHTML 'H' /* HTML */
+#define GOPHER_SOUND 's'
+#define GOPHER_WWW 'w' /* W3 address */
+#define GOPHER_IMAGE 'I'
+#define GOPHER_TN3270 'T'
+#define GOPHER_INFO 'i'
+#define GOPHER_DUPLICATE '+'
+#define GOPHER_PLUS_IMAGE ':' /* Addition from Gopher Plus */
+#define GOPHER_PLUS_MOVIE ';'
+#define GOPHER_PLUS_SOUND '<'
+#define GOPHER_PLUS_PDF 'P'
+
+#include <HTFormat.h>
+
+/*
+ * Hypertext object building machinery.
+ */
+#include <HTML.h>
+
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYLeaks.h>
+
+#define PUTC(c) (*targetClass.put_character)(target, c)
+#define PUTS(s) (*targetClass.put_string)(target, s)
+#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
+#define END(e) (*targetClass.end_element)(target, e, 0)
+#define FREE_TARGET (*targetClass._free)(target)
+
+#define NEXT_CHAR HTGetCharacter()
+
+/*
+ * Module-wide variables.
+ */
+static int s; /* Socket for gopher or CSO host */
+
+struct _HTStructured {
+ const HTStructuredClass *isa; /* For gopher streams */
+ /* ... */
+};
+
+static HTStructured *target; /* the new gopher hypertext */
+static HTStructuredClass targetClass; /* Its action routines */
+
+struct _HTStream {
+ HTStreamClass *isa; /* For form-based CSO gateway - FM */
+};
+
+typedef struct _CSOfield_info { /* For form-based CSO gateway - FM */
+ struct _CSOfield_info *next;
+ char *name;
+ char *attributes;
+ char *description;
+ int id;
+ int lookup;
+ int indexed;
+ int url;
+ int max_size;
+ int defreturn;
+ int explicit_return;
+ int reserved;
+ int gpublic;
+ char name_buf[16]; /* Avoid malloc if we can */
+ char desc_buf[32]; /* Avoid malloc if we can */
+ char attr_buf[80]; /* Avoid malloc if we can */
+} CSOfield_info;
+
+static CSOfield_info *CSOfields = NULL; /* For form-based CSO gateway - FM */
+
+typedef struct _CSOformgen_context { /* For form-based CSO gateway - FM */
+ const char *host;
+ const char *seek;
+ CSOfield_info *fld;
+ int port;
+ int cur_line;
+ int cur_off;
+ int rep_line;
+ int rep_off;
+ int public_override;
+ int field_select;
+} CSOformgen_context;
+
+/* Matrix of allowed characters in filenames
+ * =========================================
+ */
+static BOOL acceptable_html[256];
+static BOOL acceptable_file[256];
+static BOOL acceptable_inited = NO;
+
+static void init_acceptable(void)
+{
+ unsigned int i;
+ const char *good =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";
+
+ for (i = 0; i < 256; i++) {
+ acceptable_html[i] = NO;
+ acceptable_file[i] = NO;
+ }
+ for (; *good; good++) {
+ acceptable_html[(unsigned int) *good] = YES;
+ acceptable_file[(unsigned int) *good] = YES;
+ }
+ for (good = ";?=#"; *good; ++good) {
+ acceptable_html[(unsigned int) *good] = YES;
+ }
+ acceptable_inited = YES;
+}
+
+/* Decode one hex character
+ * ========================
+ */
+static const char hex[17] = "0123456789abcdef";
+
+static char from_hex(int c)
+{
+ return (char) ((c >= '0') && (c <= '9') ? c - '0'
+ : (c >= 'A') && (c <= 'F') ? c - 'A' + 10
+ : (c >= 'a') && (c <= 'f') ? c - 'a' + 10
+ : 0);
+}
+
+/* Paste in an Anchor
+ * ==================
+ *
+ * The title of the destination is set, as there is no way
+ * of knowing what the title is when we arrive.
+ *
+ * On entry,
+ * HT is in append mode.
+ * text points to the text to be put into the file, 0 terminated.
+ * addr points to the hypertext reference address 0 terminated.
+ */
+BOOLEAN HT_Is_Gopher_URL = FALSE;
+
+static void write_anchor(const char *text, const char *addr)
+{
+ BOOL present[HTML_A_ATTRIBUTES];
+ const char *value[HTML_A_ATTRIBUTES];
+
+ int i;
+
+ for (i = 0; i < HTML_A_ATTRIBUTES; i++)
+ present[i] = 0;
+ present[HTML_A_HREF] = YES;
+ ((const char **) value)[HTML_A_HREF] = addr;
+ present[HTML_A_TITLE] = YES;
+ ((const char **) value)[HTML_A_TITLE] = text;
+
+ CTRACE((tfp, "HTGopher: adding URL: %s\n", addr));
+
+ HT_Is_Gopher_URL = TRUE; /* tell HTML.c that this is a Gopher URL */
+ (*targetClass.start_element) (target, HTML_A, present,
+ (const char **) value, -1, 0);
+
+ PUTS(text);
+ END(HTML_A);
+}
+
+/* Parse a Gopher Menu document
+ * ============================
+ */
+static void parse_menu(const char *arg GCC_UNUSED,
+ HTParentAnchor *anAnchor)
+{
+ char gtype;
+ char this_type;
+ int ich;
+ char line[BIG];
+ char *name = NULL, *selector = NULL; /* Gopher menu fields */
+ char *host = NULL;
+ char *port;
+ char *p = line;
+ const char *title;
+ int bytes = 0;
+ int BytesReported = 0;
+ char buffer[128];
+ BOOL *valid_chars;
+
+#define TAB '\t'
+#define HEX_ESCAPE '%'
+
+ START(HTML_HTML);
+ PUTC('\n');
+ START(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_TITLE);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else
+ PUTS(GOPHER_MENU_TITLE);
+ END(HTML_TITLE);
+ PUTC('\n');
+ END(HTML_HEAD);
+ PUTC('\n');
+
+ START(HTML_BODY);
+ PUTC('\n');
+ START(HTML_H1);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else
+ PUTS(GOPHER_MENU_TITLE);
+ END(HTML_H1);
+ PUTC('\n');
+ START(HTML_PRE);
+ PUTC('\n'); /* newline after HTML_PRE forces split-line */
+ this_type = GOPHER_ERROR;
+ while ((ich = NEXT_CHAR) != EOF) {
+
+ if (interrupted_in_htgetcharacter) {
+ CTRACE((tfp,
+ "HTGopher: Interrupted in HTGetCharacter, apparently.\n"));
+ goto end_html;
+ }
+
+ if ((char) ich != LF) {
+ const char *ss = NULL;
+
+ /*
+ * Help the -source output to look like the HTML equivalent of the
+ * Gopher menu.
+ */
+ if (dump_output_immediately
+ && HTOutputFormat == WWW_DUMP) {
+ if (ich == '<') {
+ ss = "&lt;";
+ } else if (ich == '>') {
+ ss = "&gt;";
+ } else if (ich == '&') {
+ ss = "&amp;";
+ }
+ }
+ if (ss != NULL) {
+ if ((p + 5) < &line[BIG - 1]) {
+ while (*ss != '\0') {
+ *p++ = *ss++;
+ }
+ }
+ } else {
+ *p = (char) ich; /* Put character in line */
+ if (p < &line[BIG - 1])
+ p++;
+ }
+
+ } else {
+ *p++ = '\0'; /* Terminate line */
+ bytes += (int) (p - line); /* add size */
+ p = line; /* Scan it to parse it */
+ port = 0; /* Flag "not parsed" */
+ CTRACE((tfp, "HTGopher: Menu item: %s\n", line));
+ gtype = *p++;
+
+ if (bytes > BytesReported + 1024) {
+ sprintf(buffer, TRANSFERRED_X_BYTES, bytes);
+ HTProgress(buffer);
+ BytesReported = bytes;
+ }
+
+ /* Break on line with a dot by itself */
+ if ((gtype == '.') && ((*p == '\r') || (*p == 0)))
+ break;
+
+ if (gtype && *p) {
+ name = p;
+ selector = StrChr(name, TAB);
+ if (selector) {
+ *selector++ = '\0'; /* Terminate name */
+ /*
+ * Gopher+ Type=0+ objects can be binary, and will
+ * have 9 or 5 beginning their selector. Make sure
+ * we don't trash the terminal by treating them as
+ * text. - FM
+ */
+ if (gtype == GOPHER_TEXT && (*selector == GOPHER_BINARY ||
+ *selector == GOPHER_PCBINARY))
+ gtype = *selector;
+ host = StrChr(selector, TAB);
+ if (host) {
+ *host++ = '\0'; /* Terminate selector */
+ port = StrChr(host, TAB);
+ if (port) {
+ char *junk;
+
+ port[0] = ':'; /* delimit host a la W3 */
+ junk = StrChr(port, TAB);
+ if (junk)
+ *junk = '\0'; /* Chop port */
+ if ((port[1] == '0') && (!port[2]))
+ port[0] = '\0'; /* 0 means none */
+ } /* no port */
+ } /* host ok */
+ } /* selector ok */
+ }
+ /* gtype and name ok */
+ /* Nameless files are a separator line */
+ if (name != NULL && gtype == GOPHER_TEXT) {
+ int i = (int) strlen(name) - 1;
+
+ while (name[i] == ' ' && i >= 0)
+ name[i--] = '\0';
+ if (i < 0)
+ gtype = GOPHER_INFO;
+ }
+
+ if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
+ PUTS("(HTML) ");
+ write_anchor(name, selector);
+
+ } else if (gtype == GOPHER_INFO) {
+ /* Information or separator line */
+ PUTS(" ");
+ PUTS(name);
+
+ } else if (port && /* Other types need port */
+ (gtype != GOPHER_DUPLICATE ||
+ this_type != GOPHER_ERROR)) {
+ char *address = 0;
+ const char *format = *selector ? "%s//%s@%s/" : "%s//%s/";
+
+ if (gtype == GOPHER_TELNET) {
+ PUTS(" (TEL) ");
+ if (*selector == '/')
+ ++selector;
+ HTSprintf0(&address, format, STR_TELNET_URL, selector, host);
+ } else if (gtype == GOPHER_TN3270) {
+ PUTS("(3270) ");
+ if (*selector == '/')
+ ++selector;
+ HTSprintf0(&address, format, STR_TN3270_URL, selector, host);
+ } else { /* If parsed ok */
+ char *r;
+
+ switch (gtype) {
+ case GOPHER_TEXT:
+ PUTS("(FILE) ");
+ break;
+ case GOPHER_MENU:
+ PUTS(" (DIR) ");
+ break;
+ case GOPHER_DUPLICATE:
+ PUTS(" (+++) ");
+ break;
+ case GOPHER_CSO:
+ PUTS(" (CSO) ");
+ break;
+ case GOPHER_PCBINARY:
+ PUTS(" (BIN) ");
+ break;
+ case GOPHER_UUENCODED:
+ PUTS(" (UUE) ");
+ break;
+ case GOPHER_INDEX:
+ PUTS(" (?) ");
+ break;
+ case GOPHER_BINARY:
+ PUTS(" (BIN) ");
+ break;
+ case GOPHER_GIF:
+ case GOPHER_IMAGE:
+ case GOPHER_PLUS_IMAGE:
+ PUTS(" (IMG) ");
+ break;
+ case GOPHER_SOUND:
+ case GOPHER_PLUS_SOUND:
+ PUTS(" (SND) ");
+ break;
+ case GOPHER_MACBINHEX:
+ PUTS(" (HQX) ");
+ break;
+ case GOPHER_HTML:
+ case GOPHER_CHTML:
+ PUTS("(HTML) ");
+ break;
+ case 'm':
+ PUTS("(MIME) ");
+ break;
+ case GOPHER_PLUS_MOVIE:
+ PUTS(" (MOV) ");
+ break;
+ case GOPHER_PLUS_PDF:
+ PUTS(" (PDF) ");
+ break;
+ default:
+ PUTS("(UNKN) ");
+ break;
+ }
+
+ if (gtype != GOPHER_DUPLICATE)
+ this_type = gtype;
+
+ HTSprintf0(&address, "//%s/%c", host, this_type);
+ if (gtype == GOPHER_HTML) {
+ valid_chars = acceptable_html;
+ if (*selector == '/')
+ ++selector;
+ } else {
+ valid_chars = acceptable_file;
+ }
+
+ for (r = selector; *r; r++) { /* Encode selector string */
+ if (valid_chars[UCH(*r)]) {
+ HTSprintf(&address, "%c", *r);
+ } else {
+ HTSprintf(&address, "%c%c%c",
+ HEX_ESCAPE, /* Means hex coming */
+ hex[(TOASCII(*r)) >> 4],
+ hex[(TOASCII(*r)) & 15]);
+ }
+ }
+ }
+ /* Error response from Gopher doesn't deserve to
+ be a hyperlink. */
+ if (strcmp(address, "gopher://error.host:1/0"))
+ write_anchor(name, address);
+ else
+ PUTS(name);
+ FREE(address);
+ } else { /* parse error */
+ CTRACE((tfp, "HTGopher: Bad menu item (type %d, port %s).\n",
+ gtype, NonNull(port)));
+ PUTS(line);
+
+ } /* parse error */
+
+ PUTC('\n');
+ p = line; /* Start again at beginning of line */
+
+ } /* if end of line */
+
+ } /* Loop over characters */
+
+ end_html:
+ END(HTML_PRE);
+ PUTC('\n');
+ END(HTML_BODY);
+ PUTC('\n');
+ END(HTML_HTML);
+ PUTC('\n');
+ FREE_TARGET;
+
+ return;
+}
+
+/* Parse a Gopher CSO document from an ISINDEX query.
+ * ==================================================
+ *
+ * Accepts an open socket to a CSO server waiting to send us
+ * data and puts it on the screen in a reasonable manner.
+ *
+ * Perhaps this data can be automatically linked to some
+ * other source as well???
+ *
+ * Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu
+ * on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret,
+ * secret@dxcern.cern.ch .
+ */
+static void parse_cso(const char *arg,
+ HTParentAnchor *anAnchor)
+{
+ int ich;
+ char line[BIG];
+ char *p = line;
+ char *first_colon, *second_colon, last_char = '\0';
+ const char *title;
+
+ START(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_TITLE);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else
+ PUTS(GOPHER_CSO_SEARCH_RESULTS);
+ END(HTML_TITLE);
+ PUTC('\n');
+ END(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_H1);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else {
+ PUTS(arg);
+ PUTS(GOPHER_SEARCH_RESULTS);
+ }
+ END(HTML_H1);
+ PUTC('\n');
+ START(HTML_PRE);
+
+ /*
+ * Start grabbing chars from the network.
+ */
+ while ((ich = NEXT_CHAR) != EOF) {
+ if ((char) ich != LF) {
+ *p = (char) ich; /* Put character in line */
+ if (p < &line[BIG - 1])
+ p++;
+ } else {
+ *p = '\0'; /* Terminate line */
+ p = line; /* Scan it to parse it */
+ /*
+ * OK we now have a line in 'p'. Lets parse it and print it.
+ */
+
+ /*
+ * Break on line that begins with a 2. It's the end of data.
+ */
+ if (*p == '2')
+ break;
+
+ /*
+ * Lines beginning with 5 are errors. Print them and quit.
+ */
+ if (*p == '5') {
+ START(HTML_H2);
+ PUTS(p + 4);
+ END(HTML_H2);
+ break;
+ }
+
+ if (*p == '-') {
+ /*
+ * Data lines look like -200:#:
+ * where # is the search result number and can be multiple
+ * digits (infinite?).
+ * Find the second colon and check the digit to the left of it
+ * to see if they are different. If they are then a different
+ * person is starting. Make this line an <h2>.
+ */
+
+ /*
+ * Find the second_colon.
+ */
+ second_colon = NULL;
+ first_colon = StrChr(p, ':');
+ if (first_colon != NULL) {
+ second_colon = StrChr(first_colon + 1, ':');
+ }
+
+ if (second_colon != NULL) { /* error check */
+
+ if (*(second_colon - 1) != last_char)
+ /* print separator */
+ {
+ END(HTML_PRE);
+ START(HTML_H2);
+ }
+
+ /*
+ * Right now the record appears with the alias (first line)
+ * as the header and the rest as <pre> text.
+ *
+ * It might look better with the name as the header and the
+ * rest as a <ul> with <li> tags. I'm not sure whether the
+ * name field comes in any special order or if its even
+ * required in a record, so for now the first line is the
+ * header no matter what it is (it's almost always the
+ * alias).
+ *
+ * A <dl> with the first line as the <DT> and the rest as
+ * some form of <DD> might good also?
+ */
+
+ /*
+ * Print data.
+ */
+ PUTS(second_colon + 1);
+ PUTC('\n');
+
+ if (*(second_colon - 1) != last_char)
+ /* end separator */
+ {
+ END(HTML_H2);
+ START(HTML_PRE);
+ }
+
+ /*
+ * Save the char before the second colon for comparison on
+ * the next pass.
+ */
+ last_char = *(second_colon - 1);
+
+ } /* end if second_colon */
+ } /* end if *p == '-' */
+ } /* if end of line */
+
+ } /* Loop over characters */
+
+ /* end the text block */
+ PUTC('\n');
+ END(HTML_PRE);
+ PUTC('\n');
+ FREE_TARGET;
+
+ return; /* all done */
+} /* end of procedure */
+
+/* Display a Gopher CSO ISINDEX cover page.
+ * ========================================
+ */
+static void display_cso(const char *arg,
+ HTParentAnchor *anAnchor)
+{
+ const char *title;
+
+ START(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_TITLE);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else
+ PUTS(GOPHER_CSO_INDEX);
+ END(HTML_TITLE);
+ PUTC('\n');
+ START(HTML_ISINDEX);
+ PUTC('\n');
+ END(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_H1);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else {
+ PUTS(arg);
+ PUTS(INDEX_SEGMENT);
+ }
+ END(HTML_H1);
+ PUTS(GOPHER_CSO_INDEX_SUBTITLE);
+ START(HTML_P);
+ PUTS(GOPHER_CSO_SOLICIT_KEYWORDS);
+ START(HTML_P);
+ PUTS(SEGMENT_KEYWORDS_WILL);
+ PUTS(SEGMENT_PERSONS_DB_NAME);
+
+ if (!HTAnchor_title(anAnchor))
+ HTAnchor_setTitle(anAnchor, arg);
+
+ FREE_TARGET;
+ return;
+}
+
+/* Display a Gopher Index document.
+ * ================================
+ */
+static void display_index(const char *arg,
+ HTParentAnchor *anAnchor)
+{
+ const char *title;
+
+ START(HTML_HEAD);
+ PUTC('\n');
+ PUTC('\n');
+ START(HTML_TITLE);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else
+ PUTS(GOPHER_INDEX_TITLE);
+ END(HTML_TITLE);
+ PUTC('\n');
+ START(HTML_ISINDEX);
+ PUTC('\n');
+ END(HTML_HEAD);
+ PUTC('\n');
+ START(HTML_H1);
+ if ((title = HTAnchor_title(anAnchor)))
+ PUTS(title);
+ else {
+ PUTS(arg);
+ PUTS(INDEX_SEGMENT);
+ }
+ END(HTML_H1);
+ PUTS(GOPHER_INDEX_SUBTITLE);
+ START(HTML_P);
+ PUTS(GOPHER_SOLICIT_KEYWORDS);
+
+ if (!HTAnchor_title(anAnchor))
+ HTAnchor_setTitle(anAnchor, arg);
+
+ FREE_TARGET;
+ return;
+}
+
+/* De-escape a selector into a command.
+ * ====================================
+ *
+ * The % hex escapes are converted. Otherwise, the string is copied.
+ */
+static void de_escape(char *command, const char *selector)
+{
+ const char *p = selector;
+ char *q;
+
+ if (command == NULL)
+ outofmem(__FILE__, "HTLoadGopher");
+
+ q = command;
+ while (*p) { /* Decode hex */
+ if (*p == HEX_ESCAPE) {
+ char c;
+ unsigned int b;
+
+ p++;
+ c = *p++;
+ b = UCH(from_hex(c));
+ c = *p++;
+ if (!c)
+ break; /* Odd number of chars! */
+ *q++ = (char) FROMASCII((b << 4) + UCH(from_hex(c)));
+ } else {
+ *q++ = *p++; /* Record */
+ }
+ }
+ *q = '\0'; /* Terminate command */
+}
+
+/* Free the CSOfields structures. - FM
+ * ===================================
+ */
+static void free_CSOfields(void)
+{
+ CSOfield_info *cur = CSOfields;
+ CSOfield_info *prev;
+
+ while (cur) {
+ if (cur->name != cur->name_buf)
+ FREE(cur->name);
+ if (cur->attributes != cur->attr_buf)
+ FREE(cur->attributes);
+ if (cur->description != cur->desc_buf)
+ FREE(cur->description);
+ prev = cur;
+ cur = cur->next;
+ FREE(prev);
+ }
+
+ return;
+}
+
+/* Interpret CSO/PH form template keys. - FM
+ * =========================================
+ */
+static void interpret_cso_key(const char *key,
+ char *buf,
+ size_t bufsize,
+ int *length,
+ CSOformgen_context * ctx,
+ HTStream *Target)
+{
+ CSOfield_info *fld;
+
+ if ((fld = ctx->fld) != 0) {
+ /*
+ * Most substitutions only recognized inside of loops.
+ */
+ int error = 0;
+
+ if (0 == StrNCmp(key, "$(FID)", 6)) {
+ sprintf(buf, "%d", fld->id);
+ } else if (0 == StrNCmp(key, "$(FDESC)", 8)) {
+ sprintf(buf, "%.2046s", fld->description);
+ } else if (0 == StrNCmp(key, "$(FDEF)", 7)) {
+ strcpy(buf, fld->defreturn ? " checked" : "");
+ } else if (0 == StrNCmp(key, "$(FNDX)", 7)) {
+ strcpy(buf, fld->indexed ? "*" : "");
+ } else if (0 == StrNCmp(key, "$(FSIZE)", 8)) {
+ sprintf(buf, " size=%d maxlength=%d",
+ fld->max_size > 55 ? 55 : fld->max_size,
+ fld->max_size);
+ } else if (0 == StrNCmp(key, "$(FSIZE2)", 9)) {
+ sprintf(buf, " maxlength=%d", fld->max_size);
+ } else {
+ error = 1;
+ }
+ if (!error) {
+ *length = (int) strlen(buf);
+ return;
+ }
+ }
+ buf[0] = '\0';
+ if (0 == StrNCmp(key, "$(NEXTFLD)", 10)) {
+ if (!ctx->fld)
+ fld = CSOfields;
+ else
+ fld = ctx->fld->next;
+ switch (ctx->field_select) {
+ case 0:
+ /*
+ * 'Query' fields, public and lookup attributes.
+ */
+ for (; fld; fld = fld->next)
+ if (fld->gpublic && (fld->lookup == 1))
+ break;
+ break;
+ case 1:
+ /*
+ * 'Query' fields, accept lookup attribute.
+ */
+ for (; fld; fld = fld->next)
+ if (fld->lookup == 1)
+ break;
+ break;
+ case 2:
+ /*
+ * 'Return' fields, public only.
+ */
+ for (; fld; fld = fld->next)
+ if (fld->gpublic)
+ break;
+ break;
+ case 3:
+ /*
+ * All fields.
+ */
+ break;
+ }
+ if (fld) {
+ ctx->cur_line = ctx->rep_line;
+ ctx->cur_off = ctx->rep_off;
+ }
+ ctx->fld = fld;
+
+ } else if ((0 == StrNCmp(key, "$(QFIELDS)", 10)) ||
+ (0 == StrNCmp(key, "$(RFIELDS)", 10))) {
+ /*
+ * Begin iteration sequence.
+ */
+ ctx->rep_line = ctx->cur_line;
+ ctx->rep_off = ctx->cur_off;
+ ctx->fld = (CSOfield_info *) 0;
+ ctx->seek = "$(NEXTFLD)";
+ ctx->field_select = (key[2] == 'Q') ? 0 : 2;
+ if (ctx->public_override)
+ ctx->field_select++;
+
+ } else if (0 == StrNCmp(key, "$(NAMEFLD)", 10)) {
+ /*
+ * Special, locate name field. Flag lookup so QFIELDS will skip it.
+ */
+ for (fld = CSOfields; fld; fld = fld->next)
+ if (strcmp(fld->name, "name") == 0 ||
+ strcmp(fld->name, "Name") == 0) {
+ if (fld->lookup)
+ fld->lookup = 2;
+ break;
+ }
+ ctx->fld = fld;
+ } else if (0 == StrNCmp(key, "$(HOST)", 7)) {
+ strcpy(buf, ctx->host);
+ } else if (0 == StrNCmp(key, "$(PORT)", 7)) {
+ sprintf(buf, "%d", ctx->port);
+ } else {
+ /*
+ * No match, dump key to buffer so client sees it for debugging.
+ */
+ size_t out = 0;
+
+ while (*key && (*key != ')')) {
+ buf[out++] = (*key++);
+ if (out > bufsize - 2) {
+ buf[out] = '\0';
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ out = 0;
+ }
+ }
+ buf[out++] = ')';
+ buf[out] = '\0';
+ }
+ *length = (int) strlen(buf);
+ return;
+}
+
+/* Parse the elements in a CSO/PH fields structure. - FM
+ * =====================================================
+ */
+static int parse_cso_field_info(CSOfield_info *blk)
+{
+ char *info, *max_spec;
+
+ /*
+ * Initialize all fields to default values.
+ */
+ blk->indexed = blk->lookup = blk->reserved = blk->max_size = blk->url = 0;
+ blk->defreturn = blk->explicit_return = blk->gpublic = 0;
+
+ /*
+ * Search for keywords in info string and set values. Attributes are
+ * converted to all lower-case for comparison.
+ */
+ info = blk->attributes;
+ LYLowerCase(info);
+ if (strstr(info, "indexed "))
+ blk->indexed = 1;
+ if (strstr(info, "default "))
+ blk->defreturn = 1;
+ if (strstr(info, "public "))
+ blk->gpublic = 1;
+ if (strstr(info, "lookup "))
+ blk->lookup = 1;
+ if (strstr(info, "url ")) {
+ blk->url = 1;
+ blk->defreturn = 1;
+ }
+ max_spec = strstr(info, "max ");
+ if (max_spec) {
+ sscanf(&max_spec[4], "%d", &blk->max_size);
+ } else {
+ blk->max_size = 32;
+ }
+
+ return 0;
+}
+
+/* Parse a reply from a CSO/PH fields request. - FM
+ * ================================================
+ */
+static int parse_cso_fields(char *buf,
+ size_t size)
+{
+ int ich;
+ char *p = buf;
+ int i, code = 0, prev_code;
+ size_t alen;
+ char *indx, *name;
+ CSOfield_info *last, *newf;
+
+ last = CSOfields = (CSOfield_info *) 0;
+ prev_code = -2555;
+ buf[0] = '\0';
+
+ /*
+ * Start grabbing chars from the network.
+ */
+ while ((ich = NEXT_CHAR) != EOF) {
+ if (interrupted_in_htgetcharacter) {
+ CTRACE((tfp,
+ "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n"));
+ free_CSOfields();
+ buf[0] = '\0';
+ return HT_INTERRUPTED;
+ }
+
+ if ((char) ich != LF) {
+ *p = (char) ich; /* Put character in buffer */
+ if (p < &buf[size - 1]) {
+ p++;
+ }
+ } else {
+ *p = '\0'; /* Terminate line */
+ p = buf; /* Scan it to parse it */
+
+ /* OK we now have a line in 'p' lets parse it.
+ */
+
+ /*
+ * Break on line that begins with a 2. It's the end of data.
+ */
+ if (*p == '2')
+ break;
+
+ /*
+ * Lines beginning with 5 are errors. Print them and quit.
+ */
+ if (*p == '5') {
+ return 5;
+ }
+
+ if (*p == '-') {
+ /*
+ * Data lines look like -200:#:
+ * where # is the search result number and can be multiple
+ * digits (infinite?).
+ */
+
+ /*
+ * Check status, ignore any non-success.
+ */
+ if (p[1] != '2')
+ continue;
+
+ /*
+ * Parse fields within returned line into status, ndx, name,
+ * data.
+ */
+ indx = NULL;
+ name = NULL;
+ for (i = 0; p[i]; i++) {
+ if (p[i] == ':') {
+ p[i] = '\0';
+ if (!indx) {
+ indx = (char *) &p[i + 1];
+ code = atoi(indx);
+ } else if (!name) {
+ name = (char *) &p[i + 1];
+ } else {
+ i++;
+ break;
+ }
+ }
+ }
+ /*
+ * Add data to field structure.
+ */
+ if (name) {
+ if (code == prev_code) {
+ /*
+ * Remaining data are description. Save in current
+ * info block.
+ */
+ if (last != NULL) {
+ alen = strlen((char *) &p[i]) + 1;
+ if (alen > sizeof(last->desc_buf)) {
+ if (last->description != last->desc_buf)
+ FREE(last->description);
+ if (!(last->description = (char *) malloc(alen))) {
+ outofmem(__FILE__, "HTLoadCSO");
+ }
+ }
+ strcpy(last->description, (char *) &p[i]);
+ }
+ } else {
+ /*
+ * Initialize new block, append to end of list to
+ * preserve order.
+ */
+ newf = typecalloc(CSOfield_info);
+
+ if (!newf) {
+ outofmem(__FILE__, "HTLoadCSO");
+ }
+
+ if (last)
+ last->next = newf;
+ else
+ CSOfields = newf;
+ last = newf;
+
+ newf->next = (CSOfield_info *) 0;
+ newf->name = newf->name_buf;
+ alen = strlen(name) + 1;
+ if (alen > sizeof(newf->name_buf)) {
+ if (!(newf->name = (char *) malloc(alen))) {
+ outofmem(__FILE__, "HTLoadCSO");
+ }
+ }
+ strcpy(newf->name, name);
+
+ newf->attributes = newf->attr_buf;
+ alen = strlen((char *) &p[i]) + 2;
+ if (alen > sizeof(newf->attr_buf)) {
+ if (!(newf->attributes = (char *) malloc(alen))) {
+ outofmem(__FILE__, "HTLoadCSO");
+ }
+ }
+ strcpy(newf->attributes, (char *) &p[i]);
+ strcpy((char *) &newf->attributes[alen - 2], " ");
+ newf->description = newf->desc_buf;
+ newf->desc_buf[0] = '\0';
+ newf->id = atoi(indx);
+ /*
+ * Scan for keywords.
+ */
+ parse_cso_field_info(newf);
+ }
+ prev_code = code;
+ } else
+ break;
+ } /* end if *p == '-' */
+ } /* if end of line */
+
+ } /* Loop over characters */
+
+ /* end the text block */
+
+ if (buf[0] == '\0') {
+ return -1; /* no response */
+ }
+ buf[0] = '\0';
+ return 0; /* all done */
+} /* end of procedure */
+
+/* Generate a form for submitting CSO/PH searches. - FM
+ * ====================================================
+ */
+static int generate_cso_form(char *host,
+ int port,
+ char *buf,
+ size_t bufsize,
+ HTStream *Target)
+{
+ int i, j, length;
+ size_t out;
+ int full_flag = 1;
+ const char *key;
+ const char *line;
+ CSOformgen_context ctx;
+ static const char *ctemplate[] =
+ {
+ "<HTML>\n<HEAD>\n<TITLE>CSO/PH Query Form for $(HOST)</TITLE>\n</HEAD>\n<BODY>",
+ "<H2><I>CSO/PH Query Form</I> for <EM>$(HOST)</EM></H2>",
+ "To search the database for a name, fill in one or more of the fields",
+ "in the form below and activate the 'Submit query' button. At least",
+ "one of the entered fields must be flagged as indexed.",
+ "<HR><FORM method=\"POST\" action=\"cso://$(HOST)/\">",
+ "[ <input type=\"submit\" value=\"Submit query\"> | ",
+ "<input type=\"reset\" value=\"Clear fields\"> ]",
+ "<P><DL>",
+ " <DT>Search parameters (* indicates indexed field):",
+ " <DD>",
+ "$(NAMEFLD) <DL COMPACT>\n <DT><I>$(FDESC)</I>$(FNDX)",
+ " <DD>Last: <input name=\"q_$(FID)\" type=\"text\" size=49$(FSIZE2)>",
+ " <DD>First: <input name=\"q_$(FID)\" type=\"text\" size=48$(FSIZE2)>",
+ "$(QFIELDS) <DT><I>$(FDESC)</I>$(FNDX)",
+ " <DD><input name=\"q_$(FID)\" type=\"text\" $(FSIZE)>\n$(NEXTFLD)",
+ " </DL>",
+ " </DL>\n<P><DL>",
+ " <DT>Output format:",
+ " <DD>Returned data option: <select name=\"return\">",
+ " <option>default<option selected>all<option>selected</select><BR>",
+ "$(RFIELDS) <input type=\"checkbox\" name=\"r_$(FID)\"$(FDEF)> $(FDESC)<BR>",
+ "$(NEXTFLD) ",
+ " </DL></FORM><HR>\n</BODY>\n</HTML>",
+ (char *) 0
+ };
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.host = host;
+ ctx.seek = (char *) 0;
+ ctx.port = port;
+ ctx.fld = (CSOfield_info *) 0;
+ ctx.public_override = full_flag;
+ /*
+ * Parse the strings in the template array to produce HTML document to send
+ * to client. First line is skipped for 'full' lists.
+ */
+ out = 0;
+ buf[out] = '\0';
+ for (i = 0; ctemplate[i]; i++) {
+ /*
+ * Search the current string for substitution, flagged by $(
+ */
+ for (line = ctemplate[i], j = 0; line[j]; j++) {
+ if ((line[j] == '$') && (line[j + 1] == '(')) {
+ /*
+ * Command detected, flush output buffer and find closing ')'
+ * that delimits the command.
+ */
+ buf[out] = '\0';
+ if (out > 0)
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ for (key = &line[j]; line[j + 1] && (line[j] != ')'); j++) {
+ ;
+ }
+ /*
+ * Save context, interpret command and restore updated context.
+ */
+ ctx.cur_line = i;
+ ctx.cur_off = j;
+ interpret_cso_key(key, buf, bufsize, &length, &ctx, Target);
+ i = ctx.cur_line;
+ j = ctx.cur_off;
+ line = ctemplate[i];
+ out = (size_t) length;
+
+ if (ctx.seek) {
+ /*
+ * Command wants us to skip (forward) to indicated token.
+ * Start at current position.
+ */
+ size_t slen = strlen(ctx.seek);
+
+ for (; ctemplate[i]; i++) {
+ for (line = ctemplate[i]; line[j]; j++) {
+ if (line[j] == '$')
+ if (0 == StrNCmp(ctx.seek, &line[j], slen)) {
+ if (j == 0)
+ j = (int) strlen(ctemplate[--i]) - 1;
+ else
+ --j;
+ line = ctemplate[i];
+ ctx.seek = (char *) 0;
+ break;
+ }
+ }
+ if (!ctx.seek)
+ break;
+ j = 0;
+ }
+ if (ctx.seek) {
+ char *temp = 0;
+
+ HTSprintf0(&temp, GOPHER_CSO_SEEK_FAILED, ctx.seek);
+ (*Target->isa->put_block) (Target, temp, (int) strlen(temp));
+ FREE(temp);
+ }
+ }
+ } else {
+ /*
+ * Non-command text, add to output buffer.
+ */
+ buf[out++] = line[j];
+ if (out > (bufsize - 3)) {
+ buf[out] = '\0';
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ out = 0;
+ }
+ }
+ }
+ buf[out++] = '\n';
+ buf[out] = '\0';
+ }
+ if (out > 0)
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+
+ return 0;
+}
+
+/* Generate a results report for CSO/PH form-based searches. - FM
+ * ==============================================================
+ */
+static int generate_cso_report(HTStream *Target)
+{
+ int ich;
+ char line[BIG];
+ char *buf = 0;
+ char *p = line, *href = NULL;
+ int i, prev_ndx, ndx;
+ char *rcode, *ndx_str, *fname, *fvalue, *l;
+ CSOfield_info *fld;
+ BOOL stop = FALSE;
+
+ /*
+ * Read lines until non-negative status.
+ */
+ prev_ndx = -100;
+ /*
+ * Start grabbing chars from the network.
+ */
+ while (!stop && (ich = NEXT_CHAR) != EOF) {
+ if (interrupted_in_htgetcharacter) {
+ CTRACE((tfp,
+ "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n"));
+ _HTProgress(CONNECTION_INTERRUPTED);
+ goto end_CSOreport;
+ }
+
+ if ((char) ich != LF) {
+ *p = (char) ich; /* Put character in line */
+ if (p < &line[BIG - 1]) {
+ p++;
+ }
+ } else {
+ *p = '\0'; /* Terminate line */
+ /*
+ * OK we now have a line. Load it as 'p' and parse it.
+ */
+ p = line;
+ if (p[0] != '-' && p[0] != '1') {
+ stop = TRUE;
+ }
+ rcode = (p[0] == '-') ? &p[1] : p;
+ ndx_str = fname = NULL;
+ for (i = 0; p[i] != '\0'; i++) {
+ if (p[i] == ':') {
+ p[i] = '\0';
+ fname = &p[i + 1];
+ if (ndx_str) {
+ break;
+ }
+ ndx_str = fname;
+ }
+ }
+ if (ndx_str) {
+ ndx = atoi(ndx_str);
+ if (prev_ndx != ndx) {
+ if (prev_ndx != -100) {
+ HTSprintf0(&buf, "</DL></DL>\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ if (ndx == 0) {
+ HTSprintf0(&buf,
+ "<HR><DL><DT>Information/status<DD><DL><DT>\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ } else {
+ HTSprintf0(&buf,
+ "<HR><DL><DT>Entry %d:<DD><DL COMPACT><DT>\n", ndx);
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ prev_ndx = ndx;
+ }
+ } else {
+ HTSprintf0(&buf, "<DD>%s\n", rcode);
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ continue;
+ }
+ if ((*rcode >= '2') && (*rcode <= '5') && (fname != ndx_str)) {
+ while (*fname == ' ') {
+ fname++; /* trim leading spaces */
+ }
+ for (fvalue = fname; *fvalue; fvalue++) {
+ if (*fvalue == ':') {
+ *fvalue++ = '\0';
+ i = (int) strlen(fname) - 1;
+ while (i >= 0 && fname[i] == ' ') {
+ fname[i--] = '\0'; /* trim trailing */
+ }
+ break;
+ }
+ }
+ if (fvalue) {
+ while (*fvalue == ' ') {
+ fvalue++; /* trim leading spaces */
+ }
+ }
+ if (*fname) {
+ for (fld = CSOfields; fld; fld = fld->next) {
+ if (!strcmp(fld->name, fname)) {
+ if (fld->description) {
+ fname = fld->description;
+ }
+ break;
+ }
+ }
+ if (fld && fld->url) {
+ HTSprintf0(&buf,
+ "<DT><I>%s</I><DD><A HREF=\"%s\">%s</A>\n",
+ fname, fvalue, fvalue);
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ } else {
+ HTSprintf0(&buf, "<DT><I>%s</I><DD>", fname);
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ buf[0] = '\0';
+ l = fvalue;
+ while (*l) {
+ if (*l == '<') {
+ StrAllocCat(buf, "&lt;");
+ l++;
+ } else if (*l == '>') {
+ StrAllocCat(buf, "&gt;");
+ l++;
+ } else if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
+ StrNCmp(l, "snews://", 8) &&
+ StrNCmp(l, "nntp://", 7) &&
+ StrNCmp(l, "snewspost:", 10) &&
+ StrNCmp(l, "snewsreply:", 11) &&
+ StrNCmp(l, "newspost:", 9) &&
+ StrNCmp(l, "newsreply:", 10) &&
+ StrNCmp(l, "ftp://", 6) &&
+ StrNCmp(l, "file:/", 6) &&
+ StrNCmp(l, "finger://", 9) &&
+ StrNCmp(l, "http://", 7) &&
+ StrNCmp(l, "https://", 8) &&
+ StrNCmp(l, "wais://", 7) &&
+ StrNCmp(l, STR_MAILTO_URL,
+ LEN_MAILTO_URL) &&
+ StrNCmp(l, "cso://", 6) &&
+ StrNCmp(l, "gopher://", 9)) {
+ HTSprintf(&buf, "%c", *l++);
+ } else {
+ StrAllocCat(buf, "<a href=\"");
+ StrAllocCopy(href, l);
+ StrAllocCat(buf, strtok(href, " \r\n\t,>)\""));
+ StrAllocCat(buf, "\">");
+ while (*l && !StrChr(" \r\n\t,>)\"", *l)) {
+ HTSprintf(&buf, "%c", *l++);
+ }
+ StrAllocCat(buf, "</a>");
+ FREE(href);
+ }
+ }
+ StrAllocCat(buf, "\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ } else {
+ HTSprintf0(&buf, "<DD>");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ buf[0] = '\0';
+ l = fvalue;
+ while (*l) {
+ if (*l == '<') {
+ StrAllocCat(buf, "&lt;");
+ l++;
+ } else if (*l == '>') {
+ StrAllocCat(buf, "&gt;");
+ l++;
+ } else if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
+ StrNCmp(l, "snews://", 8) &&
+ StrNCmp(l, "nntp://", 7) &&
+ StrNCmp(l, "snewspost:", 10) &&
+ StrNCmp(l, "snewsreply:", 11) &&
+ StrNCmp(l, "newspost:", 9) &&
+ StrNCmp(l, "newsreply:", 10) &&
+ StrNCmp(l, "ftp://", 6) &&
+ StrNCmp(l, "file:/", 6) &&
+ StrNCmp(l, "finger://", 9) &&
+ StrNCmp(l, "http://", 7) &&
+ StrNCmp(l, "https://", 8) &&
+ StrNCmp(l, "wais://", 7) &&
+ StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
+ StrNCmp(l, "cso://", 6) &&
+ StrNCmp(l, "gopher://", 9)) {
+ HTSprintf(&buf, "%c", *l++);
+ } else {
+ StrAllocCat(buf, "<a href=\"");
+ StrAllocCopy(href, l);
+ StrAllocCat(buf, strtok(href, " \r\n\t,>)\""));
+ StrAllocCat(buf, "\">");
+ while (*l && !StrChr(" \r\n\t,>)\"", *l)) {
+ HTSprintf(&buf, "%c", *l++);
+ }
+ StrAllocCat(buf, "</a>");
+ FREE(href);
+ }
+ }
+ StrAllocCat(buf, "\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ } else {
+ HTSprintf0(&buf, "<DD>%s\n", fname);
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ }
+ }
+ end_CSOreport:
+ if (prev_ndx != -100) {
+ HTSprintf0(&buf, "</DL></DL>\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ }
+ FREE(buf);
+ return 0;
+}
+
+/* CSO/PH form-based search gateway - FM HTLoadCSO
+ * =====================================
+ */
+static int HTLoadCSO(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ static const char end_form[] = "</BODY>\n</HTML>\n";
+ char *host, *cp, *data;
+ int port = CSO_PORT;
+ int status; /* tcp return */
+ bstring *command = NULL;
+ bstring *content = NULL;
+ int len, i, j, start, finish, flen, ndx;
+ int return_type, has_indexed;
+ CSOfield_info *fld;
+ char buf[2048];
+ HTFormat format_in = WWW_HTML;
+ HTStream *Target = NULL;
+
+ if (!acceptable_inited)
+ init_acceptable();
+
+ if (!arg)
+ return -3; /* Bad if no name specified */
+ if (!*arg)
+ return -2; /* Bad if name had zero length */
+ CTRACE((tfp, "HTLoadCSO: Looking for %s\n", arg));
+
+ /*
+ * Set up a socket to the server for the data.
+ */
+ status = HTDoConnect(arg, "cso", CSO_PORT, &s);
+ if (status == HT_INTERRUPTED) {
+ /*
+ * Interrupt cleanly.
+ */
+ CTRACE((tfp,
+ "HTLoadCSO: Interrupted on connect; recovering cleanly.\n"));
+ _HTProgress(CONNECTION_INTERRUPTED);
+ return HT_NOT_LOADED;
+ }
+ if (status < 0) {
+ CTRACE((tfp, "HTLoadCSO: Unable to connect to remote host for `%s'.\n",
+ arg));
+ return HTInetStatus("connect");
+ }
+
+ HTInitInput(s); /* Set up input buffering */
+
+ HTBprintf(&command, "fields%c%c", CR, LF);
+ if (TRACE) {
+ CTRACE((tfp, "HTLoadCSO: Connected, writing command `"));
+ trace_bstring(command);
+ CTRACE((tfp, "' to socket %d\n", s));
+ }
+ _HTProgress(GOPHER_SENDING_CSO_REQUEST);
+ status = (int) NETWRITE(s, BStrData(command), BStrLen(command));
+ BStrFree(command);
+ if (status < 0) {
+ CTRACE((tfp, "HTLoadCSO: Unable to send command.\n"));
+ return HTInetStatus("send");
+ }
+ _HTProgress(GOPHER_SENT_CSO_REQUEST);
+
+ /*
+ * Now read the data from the socket.
+ */
+ status = parse_cso_fields(buf, sizeof(buf));
+ if (status) {
+ NETCLOSE(s);
+ if (status == HT_INTERRUPTED) {
+ _HTProgress(CONNECTION_INTERRUPTED);
+ } else if (buf[0] != '\0') {
+ HTAlert(buf);
+ } else {
+ HTAlert(FAILED_NO_RESPONSE);
+ }
+ return HT_NOT_LOADED;
+ }
+ Target = HTStreamStack(format_in,
+ format_out,
+ sink, anAnchor);
+ if (Target == NULL) {
+ char *temp = 0;
+
+ HTSprintf0(&temp, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(format_in), HTAtom_name(format_out));
+ HTAlert(temp);
+ FREE(temp);
+ NETCLOSE(s);
+ return HT_NOT_LOADED;
+ }
+ host = HTParse(arg, "", PARSE_HOST);
+ if ((cp = HTParsePort(host, &port)) != NULL) {
+ if (port == CSO_PORT) {
+ *cp = '\0';
+ }
+ }
+ anAnchor->safe = TRUE;
+ if (isBEmpty(anAnchor->post_data)) {
+ generate_cso_form(host, port, buf, sizeof(buf), Target);
+ (*Target->isa->_free) (Target);
+ FREE(host);
+ NETCLOSE(s);
+ free_CSOfields();
+ return HT_LOADED;
+ }
+
+ HTBprintf(&command,
+ "<HTML>\n<HEAD>\n<TITLE>CSO/PH Results on %s</TITLE>\n</HEAD>\n<BODY>\n",
+ host);
+ (*Target->isa->put_block) (Target, BStrData(command), BStrLen(command));
+ BStrFree(command);
+ FREE(host);
+
+ BStrCopy(content, anAnchor->post_data);
+ assert(content != NULL);
+
+ if (BStrData(content)[BStrLen(content) - 1] != '&')
+ BStrCat0(content, "&");
+
+ data = BStrData(content);
+ len = BStrLen(content);
+ for (i = 0; i < len; i++) {
+ if (data[i] == '+') {
+ data[i] = ' ';
+ }
+ }
+
+ data = BStrData(content);
+ HTUnEscape(data); /* FIXME: could it have embedded null? */
+ len = BStrLen(content);
+
+ return_type = 0;
+ has_indexed = 0;
+ start = 0;
+ for (i = 0; i < len; i++) {
+ if (!data[i] || data[i] == '&') {
+ /*
+ * Value parsed. Unescape characters and look for first '=' to
+ * delimit field name from value.
+ */
+ flen = i - start;
+ finish = start + flen;
+ data[finish] = '\0';
+ for (j = start; j < finish; j++) {
+ if (data[j] == '=') {
+ /*
+ * data[start..j-1] is field name,
+ * [j+1..finish-1] is value.
+ */
+ if ((data[start + 1] == '_') &&
+ ((data[start] == 'r') || (data[start] == 'q'))) {
+ /*
+ * Decode fields number and lookup field info.
+ */
+ sscanf(&data[start + 2], "%d=", &ndx);
+ for (fld = CSOfields; fld; fld = fld->next) {
+ if (ndx == fld->id) {
+ if ((j + 1) >= finish)
+ break; /* ignore nulls */
+ if (data[start] == 'q') {
+ /*
+ * Append field to query line.
+ */
+ if (fld->lookup) {
+ if (fld->indexed)
+ has_indexed = 1;
+ if (isBEmpty(command)) {
+ BStrCopy0(command, "query ");
+ } else {
+ BStrCat0(command, " ");
+ }
+ HTBprintf(&command, "%s=\"%s\"",
+ fld->name, &data[j + 1]);
+ } else {
+ strcpy(buf,
+ "Warning: non-lookup field ignored<BR>\n");
+ (*Target->isa->put_block) (Target,
+ buf,
+ (int)
+ strlen(buf));
+ }
+ } else if (data[start] == 'r') {
+ fld->explicit_return = 1;
+ }
+ break;
+ }
+ }
+ } else if (!StrNCmp(&data[start], "return=", 7)) {
+ if (!strcmp(&data[start + 7], "all")) {
+ return_type = 1;
+ } else if (!strcmp(&data[start + 7], "selected")) {
+ return_type = 2;
+ }
+ }
+ }
+ }
+ start = i + 1;
+ }
+ }
+ BStrFree(content);
+ if (isBEmpty(command) || !has_indexed) {
+ NETCLOSE(s);
+ strcpy(buf,
+ "<EM>Error:</EM> At least one indexed field value must be specified!\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ strcpy(buf, "</BODY>\n</HTML>\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ (*Target->isa->_free) (Target);
+ free_CSOfields();
+ BStrFree(command);
+ return HT_LOADED;
+ }
+ /*
+ * Append return fields.
+ */
+ if (return_type == 1) {
+ BStrCat0(command, " return all");
+ } else if (return_type == 2) {
+ BStrCat0(command, " return");
+ for (fld = CSOfields; fld; fld = fld->next) {
+ if (fld->explicit_return) {
+ HTBprintf(&command, " %s", fld->name);
+ }
+ }
+ }
+ HTBprintf(&command, "%c%c", CR, LF);
+ strcpy(buf, "<H2>\n<EM>CSO/PH command:</EM> ");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ (*Target->isa->put_block) (Target, BStrData(command), BStrLen(command));
+ strcpy(buf, "</H2>\n");
+ (*Target->isa->put_block) (Target, buf, (int) strlen(buf));
+ if (TRACE) {
+ CTRACE((tfp, "HTLoadCSO: Writing command `"));
+ trace_bstring(command);
+ CTRACE((tfp, "' to socket %d\n", s));
+ }
+ status = (int) NETWRITE(s, BStrData(command), BStrLen(command));
+ BStrFree(command);
+ if (status < 0) {
+ CTRACE((tfp, "HTLoadCSO: Unable to send command.\n"));
+ free_CSOfields();
+ return HTInetStatus("send");
+ }
+ generate_cso_report(Target);
+ NETCLOSE(s);
+ (*Target->isa->put_block) (Target, end_form, (int) sizeof(end_form) - 1);
+ (*Target->isa->_free) (Target);
+ FREE(host);
+ free_CSOfields();
+ return HT_LOADED;
+}
+
+static char *link_to_URL(const char *arg)
+{
+ char *result;
+ char *next;
+ char *temp = 0;
+
+ StrAllocCopy(temp, arg);
+ HTUnEscape(temp);
+ result = temp;
+
+ /* skip past method://host */
+ if ((next = strstr(result, "://")) != 0) {
+ result = next + 3;
+ }
+ if ((next = strchr(result, '/')) != 0) {
+ result = next + 1;
+ }
+ /* check if the selector is the special html one */
+ if (!strncmp(result, "hURL:", (size_t) 5)) {
+ result += 5;
+ next = result;
+ result = temp;
+ while ((*temp++ = *next++) != 0) ;
+ } else {
+ FREE(temp);
+ result = 0;
+ }
+ return result;
+}
+
+/* Load by name. HTLoadGopher
+ * =============
+ *
+ */
+static int HTLoadGopher(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ char *hURL;
+ char *command; /* The whole command */
+ int status; /* tcp return */
+ char gtype; /* Gopher Node type */
+ char *selector; /* Selector string */
+
+ if (!acceptable_inited)
+ init_acceptable();
+
+ if (!arg)
+ return -3; /* Bad if no name specified */
+ if (!*arg)
+ return -2; /* Bad if name had zero length */
+ CTRACE((tfp, "HTGopher: Looking for %s\n", arg));
+
+ /*
+ * If it's a port 105 GOPHER_CSO gtype with no ISINDEX token ('?'), use the
+ * form-based CSO gateway (otherwise, return an ISINDEX cover page or do
+ * the ISINDEX search). - FM
+ */
+ {
+ size_t len;
+
+ if ((len = strlen(arg)) > 5) {
+ if (0 == strcmp((const char *) &arg[len - 6], ":105/2")) {
+ /* Use CSO gateway. */
+ CTRACE((tfp, "HTGopher: Passing to CSO/PH gateway.\n"));
+ return HTLoadCSO(arg, anAnchor, format_out, sink);
+ }
+ }
+ }
+
+ /*
+ * If it's a port 79/0[/...] URL, use the finger gateway. - FM
+ */
+ if (strstr(arg, ":79/0") != NULL) {
+#ifndef DISABLE_FINGER
+ CTRACE((tfp, "HTGopher: Passing to finger gateway.\n"));
+ return HTLoadFinger(arg, anAnchor, format_out, sink);
+#else /* finger is disabled */
+ HTAlert(COULD_NOT_ACCESS_DOCUMENT);
+ return HT_NOT_LOADED;
+#endif /* DISABLE_FINGER */
+ }
+
+ /*
+ * Get entity type, and selector string.
+ */
+ {
+ char *p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION);
+
+ gtype = '1'; /* Default = menu */
+ selector = p1;
+ if ((*selector++ == '/') && (*selector)) { /* Skip first slash */
+ gtype = *selector++; /* Pick up gtype */
+ }
+ if (gtype == GOPHER_INDEX) {
+ char *query;
+
+ /*
+ * Search is allowed.
+ */
+ HTAnchor_setIndex(anAnchor, anAnchor->address);
+ query = StrChr(selector, '?'); /* Look for search string */
+ if (!query || !query[1]) { /* No search required */
+ target = HTML_new(anAnchor, format_out, sink);
+ targetClass = *target->isa;
+ display_index(arg, anAnchor); /* Display "cover page" */
+ return HT_LOADED; /* Local function only */
+ }
+ *query++ = '\0'; /* Skip '?' */
+ command =
+ (char *) malloc(strlen(selector) + 1 + strlen(query) + 2 + 1);
+ if (command == NULL)
+ outofmem(__FILE__, "HTLoadGopher");
+
+ de_escape(command, selector); /* Bug fix TBL 921208 */
+
+ strcat(command, "\t");
+
+ { /* Remove plus signs 921006 */
+ char *p;
+
+ for (p = query; *p; p++) {
+ if (*p == '+')
+ *p = ' ';
+ }
+ }
+
+ de_escape(&command[strlen(command)], query); /* bug fix LJM 940415 */
+ } else if (gtype == GOPHER_CSO) {
+ char *query;
+
+ /*
+ * Search is allowed.
+ */
+ query = StrChr(selector, '?'); /* Look for search string */
+ if (!query || !query[1]) { /* No search required */
+ target = HTML_new(anAnchor, format_out, sink);
+ targetClass = *target->isa;
+ display_cso(arg, anAnchor); /* Display "cover page" */
+ return HT_LOADED; /* Local function only */
+ }
+ HTAnchor_setIndex(anAnchor, anAnchor->address);
+ *query++ = '\0'; /* Skip '?' */
+ command = (char *) malloc(strlen("query") + 1 +
+ strlen(query) + 2 + 1);
+ if (command == NULL)
+ outofmem(__FILE__, "HTLoadGopher");
+
+ de_escape(command, selector); /* Bug fix TBL 921208 */
+
+ strcpy(command, "query ");
+
+ { /* Remove plus signs 921006 */
+ char *p;
+
+ for (p = query; *p; p++) {
+ if (*p == '+')
+ *p = ' ';
+ }
+ }
+ de_escape(&command[strlen(command)], query); /* bug fix LJM 940415 */
+
+ } else { /* Not index */
+ command = (char *) malloc(strlen(selector) + 2 + 1);
+ if (command == NULL)
+ outofmem(__FILE__, "HTLoadGopher");
+
+ de_escape(command, selector);
+ }
+ FREE(p1);
+ }
+
+ {
+ char *p = command + strlen(command);
+
+ *p++ = CR; /* Macros to be correct on Mac */
+ *p++ = LF;
+ *p = '\0';
+ }
+ /*
+ * Check for link to URL
+ */
+ if ((hURL = link_to_URL(arg)) != 0) {
+ CTRACE((tfp, "gopher found link to URL '%s'\n", hURL));
+ free(hURL);
+ }
+
+ /*
+ * Set up a socket to the server for the data.
+ */
+ status = HTDoConnect(arg, "gopher", GOPHER_PORT, &s);
+ if (status == HT_INTERRUPTED) {
+ /*
+ * Interrupt cleanly.
+ */
+ CTRACE((tfp, "HTGopher: Interrupted on connect; recovering cleanly.\n"));
+ _HTProgress(CONNECTION_INTERRUPTED);
+ FREE(command);
+ return HT_NOT_LOADED;
+ }
+ if (status < 0) {
+ CTRACE((tfp, "HTGopher: Unable to connect to remote host for `%s'.\n",
+ arg));
+ FREE(command);
+ return HTInetStatus("connect");
+ }
+
+ HTInitInput(s); /* Set up input buffering */
+
+ CTRACE((tfp, "HTGopher: Connected, writing command `%s' to socket %d\n",
+ command, s));
+
+#ifdef NOT_ASCII
+ {
+ char *p;
+
+ for (p = command; *p; p++) {
+ *p = TOASCII(*p);
+ }
+ }
+#endif
+
+ _HTProgress(GOPHER_SENDING_REQUEST);
+
+ status = (int) NETWRITE(s, command, (int) strlen(command));
+ FREE(command);
+ if (status < 0) {
+ CTRACE((tfp, "HTGopher: Unable to send command.\n"));
+ return HTInetStatus("send");
+ }
+
+ _HTProgress(GOPHER_SENT_REQUEST);
+
+ /*
+ * Now read the data from the socket.
+ */
+ switch (gtype) {
+
+ case GOPHER_TEXT:
+ HTParseSocket(WWW_PLAINTEXT, format_out, anAnchor, s, sink);
+ break;
+
+ case GOPHER_HTML:
+ case GOPHER_CHTML:
+ HTParseSocket(WWW_HTML, format_out, anAnchor, s, sink);
+ break;
+
+ case GOPHER_GIF:
+ case GOPHER_IMAGE:
+ case GOPHER_PLUS_IMAGE:
+ HTParseSocket(HTAtom_for("image/gif"),
+ format_out, anAnchor, s, sink);
+ break;
+
+ case GOPHER_MENU:
+ case GOPHER_INDEX:
+ target = HTML_new(anAnchor, format_out, sink);
+ targetClass = *target->isa;
+ parse_menu(arg, anAnchor);
+ break;
+
+ case GOPHER_CSO:
+ target = HTML_new(anAnchor, format_out, sink);
+ targetClass = *target->isa;
+ parse_cso(arg, anAnchor);
+ break;
+
+ case GOPHER_SOUND:
+ case GOPHER_PLUS_SOUND:
+ HTParseSocket(WWW_AUDIO, format_out, anAnchor, s, sink);
+ break;
+
+ case GOPHER_PLUS_MOVIE:
+ HTParseSocket(HTAtom_for("video/mpeg"), format_out, anAnchor, s, sink);
+ break;
+
+ case GOPHER_PLUS_PDF:
+ HTParseSocket(HTAtom_for("application/pdf"), format_out, anAnchor,
+ s, sink);
+ break;
+
+ default:
+ {
+ HTAtom *encoding = 0;
+ const char *desc = 0;
+ HTFormat format = HTFileFormat(arg, &encoding, &desc);
+
+ /*
+ * Ignore WWW_BINARY (since that is returned by HTFileFormat when
+ * it does not have a representation), but otherwise use the
+ * result.
+ */
+ if (format != WWW_BINARY) {
+ HTParseSocket(format, format_out, anAnchor, s, sink);
+ break;
+ }
+ }
+ /* FALL-THRU */
+
+ case GOPHER_MACBINHEX:
+ case GOPHER_PCBINARY:
+ case GOPHER_UUENCODED:
+ case GOPHER_BINARY:
+ /*
+ * Specifying WWW_UNKNOWN forces dump to local disk.
+ */
+ HTParseSocket(WWW_UNKNOWN, format_out, anAnchor, s, sink);
+ break;
+
+ } /* switch(gtype) */
+
+ NETCLOSE(s);
+ return HT_LOADED;
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _HTGOPHER_C_1_INIT { "gopher", HTLoadGopher, NULL }
+GLOBALDEF(HTProtocol, HTGopher, _HTGOPHER_C_1_INIT);
+#define _HTCSO_C_1_INIT { "cso", HTLoadCSO, NULL }
+GLOBALDEF(HTProtocol, HTCSO, _HTCSO_C_1_INIT);
+#else
+GLOBALDEF HTProtocol HTGopher =
+{"gopher", HTLoadGopher, NULL};
+GLOBALDEF HTProtocol HTCSO =
+{"cso", HTLoadCSO, NULL};
+#endif /* GLOBALDEF_IS_MACRO */
+
+#endif /* not DISABLE_GOPHER */