diff options
Diffstat (limited to 'WWW/Library/Implementation/HTGopher.c')
-rw-r--r-- | WWW/Library/Implementation/HTGopher.c | 2071 |
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 = "<"; + } else if (ich == '>') { + ss = ">"; + } else if (ich == '&') { + ss = "&"; + } + } + 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, "<"); + l++; + } else if (*l == '>') { + StrAllocCat(buf, ">"); + 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, "<"); + l++; + } else if (*l == '>') { + StrAllocCat(buf, ">"); + 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 */ |