diff options
Diffstat (limited to 'gnu/usr.bin/lynx/WWW/Library/Implementation/HTGopher.c')
-rw-r--r-- | gnu/usr.bin/lynx/WWW/Library/Implementation/HTGopher.c | 2004 |
1 files changed, 2004 insertions, 0 deletions
diff --git a/gnu/usr.bin/lynx/WWW/Library/Implementation/HTGopher.c b/gnu/usr.bin/lynx/WWW/Library/Implementation/HTGopher.c new file mode 100644 index 00000000000..23e8f9d07b7 --- /dev/null +++ b/gnu/usr.bin/lynx/WWW/Library/Implementation/HTGopher.c @@ -0,0 +1,2004 @@ +/* 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. +*/ + +#include "HTUtils.h" /* Coding convention macros */ +#include "tcp.h" +#include "HTAlert.h" +#include "HTParse.h" +#include "HTTCP.h" +#include "HTFinger.h" + +/* +** Implements. +*/ +#include "HTGopher.h" + +#define HT_EM_SPACE ((char)2) /* For now */ + +#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 <ctype.h> + +#include "HTParse.h" +#include "HTFormat.h" +#include "HTTCP.h" + +#define FREE(x) if (x) {free(x); x = NULL;} + +/* +** Hypertext object building machinery. +*/ +#include "HTML.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 GOPHER_PROGRESS(foo) HTAlert(foo) + +#define NEXT_CHAR HTGetCharacter() + +/* +** Module-wide variables. +*/ +PRIVATE int s; /* Socket for gopher or CSO host */ + +struct _HTStructured { + CONST HTStructuredClass * isa; /* For gopher streams */ + /* ... */ +}; + +PRIVATE HTStructured *target; /* the new gopher hypertext */ +PRIVATE 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 public; + 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; + +PRIVATE CSOfield_info *CSOfields = NULL; /* For form-based CSO gateway - FM */ + +typedef struct _CSOformgen_context { /* For form-based CSO gateway - FM */ + char * host; + 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 +** ========================================= +*/ +PRIVATE BOOL acceptable[256]; +PRIVATE BOOL acceptable_inited = NO; + +PRIVATE void init_acceptable NOARGS +{ + unsigned int i; + char * good = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$"; + for(i = 0; i < 256; i++) + acceptable[i] = NO; + for(; *good; good++) + acceptable[(unsigned int)*good] = YES; + acceptable_inited = YES; +} + +/* Decode one hex character +** ======================== +*/ +PRIVATE CONST char hex[17] = "0123456789abcdef"; + +PRIVATE char from_hex ARGS1(char, c) +{ + return (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 refernce address 0 terminated. +*/ +PUBLIC BOOLEAN HT_Is_Gopher_URL=FALSE; + +PRIVATE void write_anchor ARGS2(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; + + if(TRACE) + fprintf(stderr,"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 +** ============================ +*/ +PRIVATE void parse_menu ARGS2( + CONST char *, arg GCC_UNUSED, + HTParentAnchor *, anAnchor) +{ + char gtype; + char ch; + char line[BIG]; + char address[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]; + +#define TAB '\t' +#define HEX_ESCAPE '%' + + + START(HTML_HTML); + PUTS("\n"); + START(HTML_HEAD); + PUTS("\n"); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS("Gopher Menu"); + END(HTML_TITLE); + PUTS("\n"); + END(HTML_HEAD); + PUTS("\n"); + + START(HTML_BODY); + PUTS("\n"); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS("Gopher Menu"); + END(HTML_H1); + PUTS("\n"); + START(HTML_PRE); + while ((ch=NEXT_CHAR) != (char)EOF) { + + if (interrupted_in_htgetcharacter) { + if (TRACE) + fprintf(stderr, + "HTGopher: Interrupted in HTGetCharacter, apparently.\n"); + goto end_html; + } + + if (ch != LF) { + *p = ch; /* Put character in line */ + if (p< &line[BIG-1]) p++; + + } else { + *p++ = '\0'; /* Terminate line */ + bytes += p-line; /* add size */ + p = line; /* Scan it to parse it */ + port = 0; /* Flag "not parsed" */ + if (TRACE) + fprintf(stderr, "HTGopher: Menu item: %s\n", line); + gtype = *p++; + + if (bytes > BytesReported + 1024) { + sprintf(buffer, "Transferred %d 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 (gtype == GOPHER_TEXT) { + int i = 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 */ + if (gtype == GOPHER_TELNET) { + PUTS(" (TEL) "); + if (*selector) sprintf(address, "telnet://%s@%s/", + selector, host); + else sprintf(address, "telnet://%s/", host); + } + else if (gtype == GOPHER_TN3270) + { + PUTS("(3270) "); + if (*selector) + sprintf(address, "tn3270://%s@%s/", + selector, host); + else + sprintf(address, "tn3270://%s/", host); + } + else { /* If parsed ok */ + char *q; + char *r; + + switch(gtype) { + case GOPHER_TEXT: + PUTS("(FILE) "); + break; + case GOPHER_MENU: + PUTS(" (DIR) "); + 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; + } + + sprintf(address, "//%s/%c", host, gtype); + + q = address+ strlen(address); + for(r=selector; *r; r++) { /* Encode selector string */ + if (acceptable[(unsigned char)*r]) *q++ = *r; + else { + *q++ = HEX_ESCAPE; /* Means hex coming */ + *q++ = hex[(TOASCII(*r)) >> 4]; + *q++ = hex[(TOASCII(*r)) & 15]; + } + } + + *q++ = '\0'; /* terminate address */ + } + /* 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); + } else { /* parse error */ + if (TRACE) + fprintf(stderr, "HTGopher: Bad menu item.\n"); + PUTS(line); + + } /* parse error */ + + PUTS("\n"); + p = line; /* Start again at beginning of line */ + + } /* if end of line */ + + } /* Loop over characters */ + +end_html: + END(HTML_PRE); + PUTS("\n"); + END(HTML_BODY); + PUTS("\n"); + END(HTML_HTML); + PUTS("\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 . +*/ +PRIVATE void parse_cso ARGS2( + CONST char *, arg, + HTParentAnchor *, anAnchor) +{ + char ch; + char line[BIG]; + char *p = line; + char *second_colon, last_char='\0'; + CONST char *title; + + START(HTML_HEAD); + PUTS("\n"); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS("CSO Search Results"); + END(HTML_TITLE); + PUTS("\n"); + END(HTML_HEAD); + PUTS("\n"); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(" Search Results"); + } + END(HTML_H1); + PUTS("\n"); + START(HTML_PRE); + + /* + ** Start grabbing chars from the network. + */ + while ((ch=NEXT_CHAR) != (char)EOF) + { + if (ch != LF) + { + *p = ch; /* 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 diferent. + ** If they are then a different person is starting. + ** Make this line an <h2>. + */ + + /* + ** Find the second_colon. + */ + second_colon = strchr( strchr(p,':')+1, ':'); + + if(second_colon != NULL) { /* error check */ + + if (*(second_colon-1) != last_char) + /* print seperator */ + { + 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); + PUTS("\n"); + + if (*(second_colon-1) != last_char) + /* end seperator */ + { + 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 */ + PUTS("\n"); + END(HTML_PRE); + PUTS("\n"); + FREE_TARGET; + + return; /* all done */ +} /* end of procedure */ + +/* Display a Gopher CSO ISINDEX cover page. +** ======================================== +*/ +PRIVATE void display_cso ARGS2( + CONST char *, arg, + HTParentAnchor *, anAnchor) +{ + CONST char * title; + + START(HTML_HEAD); + PUTS("\n"); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS("CSO index"); + END(HTML_TITLE); + PUTS("\n"); + START(HTML_ISINDEX); + PUTS("\n"); + END(HTML_HEAD); + PUTS("\n"); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(" index"); + } + END(HTML_H1); + PUTS("\nThis is a searchable index of a CSO database.\n"); + START(HTML_P); + PUTS("\nPress the 's' key and enter search keywords.\n"); + START(HTML_P); + PUTS("\nThe keywords that you enter will allow you to search on a"); + PUTS(" person's name in the database.\n"); + + if (!HTAnchor_title(anAnchor)) + HTAnchor_setTitle(anAnchor, arg); + + FREE_TARGET; + return; +} + +/* Display a Gopher Index document. +** ================================ +*/ +PRIVATE void display_index ARGS2( + CONST char *, arg, + HTParentAnchor *,anAnchor) +{ + CONST char * title; + + START(HTML_HEAD); + PUTS("\n"); + PUTS("\n"); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS("Gopher index"); + END(HTML_TITLE); + PUTS("\n"); + START(HTML_ISINDEX); + PUTS("\n"); + END(HTML_HEAD); + PUTS("\n"); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(" index"); + } + END(HTML_H1); + PUTS("\nThis is a searchable Gopher index.\n"); + START(HTML_P); + PUTS("\nPlease enter search keywords.\n"); + + if (!HTAnchor_title(anAnchor)) + HTAnchor_setTitle(anAnchor, arg); + + FREE_TARGET; + return; +} + +/* De-escape a selector into a command. +** ==================================== +** +** The % hex escapes are converted. Otheriwse, the string is copied. +*/ +PRIVATE void de_escape ARGS2(char *, command, CONST char *, selector) +{ + CONST char * p = selector; + char * q = command; + if (command == NULL) + outofmem(__FILE__, "HTLoadGopher"); + while (*p) { /* Decode hex */ + if (*p == HEX_ESCAPE) { + char c; + unsigned int b; + p++; + c = *p++; + b = from_hex(c); + c = *p++; + if (!c) break; /* Odd number of chars! */ + *q++ = FROMASCII((b<<4) + from_hex(c)); + } else { + *q++ = *p++; /* Record */ + } + } + *q++ = '\0'; /* Terminate command */ +} + + +/* Free the CSOfields structures. - FM +** =================================== +*/ +PRIVATE void free_CSOfields NOPARAMS +{ + 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 +** ========================================= +*/ +PRIVATE int interpret_cso_key ARGS5( + char *, key, + char *, buf, + 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, "%s%s%s", fld->description, + ctx->public_override ? /***" "***/"" : "", + ctx->public_override ? /***fld->attributes***/"" : ""); + } 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 = strlen(buf); + return -1; + } + } + 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->public && (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->public) + 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 > sizeof(buf)-2) { + buf[out] = '\0'; + (*Target->isa->put_block)(Target, buf, strlen(buf)); + out = 0; + } + } + buf[out++] = ')'; + buf[out] = '\0'; + *length = strlen(buf); + return -1; + } + *length = strlen(buf); + return 0; +} + +/* Parse the elements in a CSO/PH fields structure. - FM +** ===================================================== +*/ +PRIVATE int parse_cso_field_info ARGS1( + CSOfield_info *, blk) +{ + int i; + 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->public = 0; + + /* + ** Search for keywords in info string and set values. Attributes + ** are converted to all lower-case for comparison. + */ + info = blk->attributes; + for (i = 0; info[i]; i++) + info[i] = TOLOWER(info[i]); + if (strstr(info, "indexed ")) + blk->indexed = 1; + if (strstr(info, "default ")) + blk->defreturn = 1; + if (strstr(info, "public ")) + blk->public = 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 +** ================================================ +*/ +PRIVATE int parse_cso_fields ARGS2( + char *, buf, + int, size) +{ + char ch; + char *p = buf; + int i, code = 0, prev_code; + size_t alen; + char *indx, *name; + CSOfield_info *last, *new; + + last = CSOfields = (CSOfield_info *) 0; + prev_code = -2555; + buf[0] = '\0'; + + /* + ** Start grabbing chars from the network. + */ + while ((ch = NEXT_CHAR) != (char)EOF) { + if (interrupted_in_htgetcharacter) { + if (TRACE) { + fprintf(stderr, + "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n"); + } + free_CSOfields(); + buf[0] = '\0'; + return HT_INTERRUPTED; + } + + if (ch != LF) { + *p = ch; /* 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') { + strcpy (buf, p); + 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. + */ + 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. + */ + new = (CSOfield_info *)calloc(1, sizeof(CSOfield_info)); + if (!new) { + outofmem(__FILE__, "HTLoadCSO"); + } + if (last) + last->next = new; + else + CSOfields = new; + last = new; + + new->next = (CSOfield_info *) 0; + new->name = new->name_buf; + alen = strlen(name) + 1; + if (alen > sizeof(new->name_buf)) { + if (!(new->name = (char *)malloc(alen))) { + outofmem(__FILE__, "HTLoadCSO"); + } + } + strcpy (new->name, name); + + new->attributes = new->attr_buf; + alen = strlen((char *)&p[i]) + 2; + if (alen > sizeof(new->attr_buf)) { + if (!(new->attributes = (char *)malloc(alen))) { + outofmem(__FILE__, "HTLoadCSO"); + } + } + strcpy(new->attributes, (char *)&p[i]); + strcpy((char *)&new->attributes[alen-2], " "); + new->description = new->desc_buf; + new->desc_buf[0] = '\0'; + new->id = atoi(indx); + /* + ** Scan for keywords. + */ + parse_cso_field_info(new); + } + 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 +** ==================================================== +*/ +PRIVATE int generate_cso_form ARGS4( + char *, host, + int, port, + char *, buf, + HTStream *, Target) +{ + int i, j, length; + size_t out; + int full_flag = 1; + char *key, *line; + CSOformgen_context ctx; + static char *template[] = { + "<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 + }; + + out = 0; + 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 = full_flag ? /***1***/ 0 : 0; template[i]; i++) { + /* + ** Search the current string for substitution, flagged by $( + */ + for (line=template[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, strlen(buf)); + out = 0; + for (key = &line[j]; line[j+1] && (line[j] != ')'); j++) + ; + /* + ** Save context, interpet command and restore updated context. + */ + ctx.cur_line = i; + ctx.cur_off = j; + interpret_cso_key(key, buf, &length, &ctx, Target); + i = ctx.cur_line; + j = ctx.cur_off; + line = template[i]; + out = length; + + if (ctx.seek) { + /* + ** Command wants us to skip (forward) to indicated token. + ** Start at current position. + */ + int slen = strlen(ctx.seek); + for (; template[i]; i++) { + for (line = template[i]; line[j]; j++) { + if (line[j] == '$') + if (0 == strncmp(ctx.seek, &line[j], slen)) { + if (j == 0) + j = strlen(template[--i])-1; + else + --j; + line = template[i]; + ctx.seek = (char *) 0; + break; + } + } + if (!ctx.seek) + break; + j = 0; + } + if (ctx.seek) { + char *temp = (char *)malloc(strlen(ctx.seek) + 20); + if (temp) { + outofmem(__FILE__, "HTLoadCSO"); + } + sprintf(temp, "Seek fail on %s\n", ctx.seek); + (*Target->isa->put_block)(Target, temp, strlen(temp)); + FREE(temp); + } + } + } else { + /* + ** Non-command text, add to output buffer. + */ + buf[out++] = line[j]; + if (out > (sizeof(buf)-3)) { + buf[out] = '\0'; + (*Target->isa->put_block)(Target, buf, strlen(buf)); + out = 0; + } + } + } + buf[out++] = '\n'; + buf[out] = '\0'; + } + if (out > 0) + (*Target->isa->put_block)(Target, buf, strlen(buf)); + + return 0; +} + +/* Generate a results report for CSO/PH form-based searches. - FM +** ============================================================== +*/ +PRIVATE int generate_cso_report ARGS2( + char *, buf, + HTStream *, Target) +{ + char ch; + char line[BIG]; + char *p = line, *href = NULL; + int len, 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 && (ch = NEXT_CHAR) != (char)EOF) { + if (interrupted_in_htgetcharacter) { + buf[0] = '\0'; + if (TRACE) { + fprintf(stderr, + "HTLoadCSO: Interrupted in HTGetCharacter, apparently.\n"); + } + _HTProgress ("Connection interrupted."); + goto end_CSOreport; + } + + if (ch != LF) { + *p = ch; /* 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; + len = strlen(p); + for (i = 0; i < len; i++) { + if (p[i] == ':') { + p[i] = '\0'; + if (!ndx_str) { + fname = ndx_str = &p[i+1]; + } else { + fname = &p[i+1]; + break; + } + } + } + if (ndx_str) { + ndx = atoi(ndx_str); + if (prev_ndx != ndx) { + if (prev_ndx != -100) { + strcpy(buf, "</DL></DL>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + if (ndx == 0) { + strcpy(buf, + "<HR><DL><DT>Information/status<DD><DL><DT>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } else { + sprintf(buf, + "<HR><DL><DT>Entry %d:<DD><DL COMPACT><DT>\n", ndx); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + prev_ndx = ndx; + } + } else { + sprintf(buf, "<DD>%s\n", rcode); + (*Target->isa->put_block)(Target, buf, 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 = 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) { + sprintf(buf, + "<DT><I>%s</I><DD><A HREF=\"%s\">%s</A>\n", + fname, fvalue, fvalue); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } else { + sprintf(buf, "<DT><I>%s</I><DD>", fname); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + i = 0; + buf[i] = '\0'; + l = fvalue; + while (*l) { + if (*l == '<') { + strcat(buf, "<"); + l++; + i += 4; + buf[i] = '\0'; + } else if (*l == '>') { + strcat(buf, ">"); + l++; + i += 4; + buf[i] = '\0'; + } else if (strncmp(l, "news:", 5) && + 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, "mailto:", 7) && + strncmp(l, "cso://", 6) && + strncmp(l, "gopher://", 9)) { + buf[i++] = *l++; + buf[i] = '\0'; + } else { + strcat(buf, "<a href=\""); + i += 9; + buf[i] = '\0'; + StrAllocCopy(href, l); + strcat(buf, strtok(href, " \r\n\t,>)\"")); + strcat(buf, "\">"); + i = strlen(buf); + while (*l && !strchr(" \r\n\t,>)\"", *l)) { + buf[i++] = *l++; + } + buf[i] = '\0'; + strcat(buf, "</a>"); + i += 4; + FREE(href); + } + } + strcat(buf, "\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + } else { + sprintf(buf, "<DD>"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + i = 0; + buf[i] = '\0'; + l = fvalue; + while (*l) { + if (*l == '<') { + strcat(buf, "<"); + l++; + i += 4; + buf[i] = '\0'; + } else if (*l == '>') { + strcat(buf, ">"); + l++; + i += 4; + buf[i] = '\0'; + } else if (strncmp(l, "news:", 5) && + 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, "mailto:", 7) && + strncmp(l, "cso://", 6) && + strncmp(l, "gopher://", 9)) { + buf[i++] = *l++; + buf[i] = '\0'; + } else { + strcat(buf, "<a href=\""); + i += 9; + buf[i] = '\0'; + StrAllocCopy(href, l); + strcat(buf, strtok(href, " \r\n\t,>)\"")); + strcat(buf, "\">"); + i = strlen(buf); + while (*l && !strchr(" \r\n\t,>)\"", *l)) { + buf[i++] = *l++; + } + buf[i] = '\0'; + strcat(buf, "</a>"); + i += 4; + FREE(href); + } + } + strcat(buf, "\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + } else { + sprintf(buf, "<DD>%s\n", fname ? fname : rcode ); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + } + } +end_CSOreport: + if (prev_ndx != -100) { + sprintf(buf, "</DL></DL>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + } + return 0; +} + +/* CSO/PH form-based search gateway - FM HTLoadCSO +** ===================================== +*/ +PRIVATE int HTLoadCSO ARGS4( + CONST char *, arg, + HTParentAnchor *, anAnchor, + HTFormat, format_out, + HTStream*, sink) +{ + char *host, *cp; + int port = CSO_PORT; + int status; /* tcp return */ + char *command = NULL; + char *content = NULL; + int len, i, j, start, finish, flen, ndx, clen; + 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 sepcified */ + if (!*arg) + return -2; /* Bad if name had zero length */ + if (TRACE) + fprintf(stderr, "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. + */ + if (TRACE) + fprintf(stderr, + "HTLoadCSO: Interrupted on connect; recovering cleanly.\n"); + _HTProgress ("Connection interrupted."); + return HT_NOT_LOADED; + } + if (status < 0) { + if (TRACE) + fprintf(stderr, + "HTLoadCSO: Unable to connect to remote host for `%s'.\n", + arg); + return HTInetStatus("connect"); + } + + HTInitInput(s); /* Set up input buffering */ + + if ((command = (char *)malloc(12)) == NULL) + outofmem(__FILE__, "HTLoadCSO"); + sprintf(command, "fields%c%c", CR, LF); + if (TRACE) + fprintf(stderr, + "HTLoadCSO: Connected, writing command `%s' to socket %d\n", + command, s); + _HTProgress ("Sending CSO/PH request."); + status = NETWRITE(s, command, (int)strlen(command)); + FREE(command); + if (status < 0) { + if (TRACE) + fprintf(stderr, "HTLoadCSO: Unable to send command.\n"); + return HTInetStatus("send"); + } + _HTProgress ("CSO/PH request sent; waiting for response."); + + /* + ** 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("No response from server!"); + } + return HT_NOT_LOADED; + } + Target = HTStreamStack(format_in, + format_out, + sink, anAnchor); + if (!Target || Target == NULL) { + char *temp = (char *)malloc(256); + if (!temp) { + outofmem(__FILE__, "HTLoadCSO"); + } + sprintf(temp, "Sorry, no known way of converting %s to %s.", + 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=strchr(host, ':')) != NULL) { + if (cp[1] >= '0' && cp[1] <= '9') { + port = atoi((cp+1)); + if (port == CSO_PORT) { + *cp = '\0'; + } + } + } + anAnchor->safe = TRUE; + if (!(anAnchor->post_data && *anAnchor->post_data)) { + generate_cso_form(host, port, buf, Target); + (*Target->isa->_free)(Target); + FREE(host); + NETCLOSE(s); + free_CSOfields(); + return HT_LOADED; + } + sprintf(buf, + "<HTML>\n<HEAD>\n<TITLE>CSO/PH Results on %s</TITLE>\n</HEAD>\n<BODY>\n", + host); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + FREE(host); + StrAllocCopy(content, anAnchor->post_data); + if (content[strlen(content)-1] != '&') + StrAllocCat(content, "&"); + len = strlen(content); + for (i = 0; i < len; i++) { + if (content[i] == '+') { + content[i] = ' '; + } + } + HTUnEscape(content); + len = strlen(content); + return_type = 0; + has_indexed = 0; + start = finish = clen = 0; + for (i = 0; i < len; i++) { + if (!content[i] || content[i] == '&') { + /* + ** Value parsed. Unescape characters and look for first '=' + ** to delimit field name from value. + */ + flen = i - start; + finish = start + flen; + content[finish] = '\0'; + for (j = start; j < finish; j++) { + if (content[j] == '=') { + /* + ** content[start..j-1] is field name, + ** [j+1..finish-1] is value. + */ + if ((content[start+1] == '_') && + ((content[start] == 'r') || (content[start] == 'q'))) { + /* + ** Decode fields number and lookup field info. + */ + sscanf (&content[start+2], "%d=", &ndx); + for (fld = CSOfields; fld; fld = fld->next) { + if (ndx==fld->id) { + if ((j+1) >= finish) + break; /* ignore nulls */ + if (content[start] == 'q') { + /* + * Append field to query line. + */ + if (fld->lookup) { + if (fld->indexed) + has_indexed = 1; + if (clen == 0) { + StrAllocCopy(command, "query "); + clen = 6; + } else { + StrAllocCat(command, " "); + clen++; + } + sprintf(buf, "%s=\"%s\"", + fld->name, &content[j+1]); + StrAllocCat(command, buf); + clen += strlen(buf); + } else { + strcpy(buf, + "Warning: non-lookup field ignored<BR>\n"); + (*Target->isa->put_block)(Target, + buf, + strlen(buf)); + } + } else if (content[start] == 'r') { + fld->explicit_return = 1; + } + break; + } + } + } else if (!strncmp(&content[start],"return=",7)) { + if (!strcmp(&content[start+7],"all")) { + return_type = 1; + } else if (!strcmp(&content[start+7],"selected")) { + return_type = 2; + } + } + } + } + start = i + 1; + } + } + FREE(content); + if ((clen == 0) || !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, strlen(buf)); + strcpy(buf, "</BODY>\n</HTML>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + (*Target->isa->_free)(Target); + free_CSOfields(); + return HT_LOADED; + } + /* + ** Append return fields. + */ + if (return_type == 1) { + StrAllocCat(command, " return all"); + clen += 11; + } else if (return_type == 2) { + StrAllocCat(command, " return"); + clen += 7; + for (fld = CSOfields; fld; fld = fld->next) { + if (fld->explicit_return) { + sprintf(buf, " %s", fld->name); + StrAllocCat(command, buf); + clen += strlen(buf); + } + } + } + sprintf(buf, "%c%c", CR, LF); + StrAllocCat(command, buf); + clen += strlen(buf); + strcpy(buf, "<H2>\n<EM>CSO/PH command:</EM> "); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + (*Target->isa->put_block)(Target, command, clen); + strcpy(buf, "</H2>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + if (TRACE) + fprintf(stderr, + "HTLoadCSO: Writing command `%s' to socket %d\n", + command, s); + status = NETWRITE(s, command, clen); + FREE(command); + if (status < 0) { + if (TRACE) + fprintf(stderr, "HTLoadCSO: Unable to send command.\n"); + free_CSOfields(); + return HTInetStatus("send"); + } + generate_cso_report(buf, Target); + NETCLOSE(s); + strcpy(buf, "</BODY>\n</HTML>\n"); + (*Target->isa->put_block)(Target, buf, strlen(buf)); + (*Target->isa->_free)(Target); + FREE(host); + free_CSOfields(); + return HT_LOADED; +} + +/* Load by name. HTLoadGopher +** ============= +** +** Bug: No decoding of strange data types as yet. +** +*/ +PRIVATE int HTLoadGopher ARGS4( + CONST char *, arg, + HTParentAnchor *, anAnchor, + HTFormat, format_out, + HTStream*, sink) +{ + 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 sepcified */ + if (!*arg) + return -2; /* Bad if name had zero length */ + if (TRACE) + fprintf(stderr, "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 + */ + { + int len; + + if ((len = strlen(arg)) > 5) { + if (0 == strcmp((CONST char *)&arg[len-6], ":105/2")) { + /* Use CSO gateway. */ + if (TRACE) + fprintf(stderr, "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) { + if (TRACE) + fprintf(stderr, "HTGopher: Passing to finger gateway.\n"); + return HTLoadFinger(arg, anAnchor, format_out, sink); + } + + /* + ** 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); + de_escape(command, selector); + } + FREE(p1); + } + + { + char * p = command + strlen(command); + *p++ = CR; /* Macros to be correct on Mac */ + *p++ = LF; + *p++ = '\0'; + } + + /* + ** Set up a socket to the server for the data. + */ + status = HTDoConnect (arg, "gopher", GOPHER_PORT, &s); + if (status == HT_INTERRUPTED) { + /* + ** Interrupt cleanly. + */ + if (TRACE) + fprintf(stderr, + "HTGopher: Interrupted on connect; recovering cleanly.\n"); + _HTProgress ("Connection interrupted."); + FREE(command); + return HT_NOT_LOADED; + } + if (status < 0) { + if (TRACE) + fprintf(stderr, + "HTGopher: Unable to connect to remote host for `%s'.\n", + arg); + FREE(command); + return HTInetStatus("connect"); + } + + HTInitInput(s); /* Set up input buffering */ + + if (TRACE) + fprintf(stderr, + "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 ("Sending Gopher request."); + + status = NETWRITE(s, command, (int)strlen(command)); + FREE(command); + if (status < 0) { + if (TRACE) + fprintf(stderr, "HTGopher: Unable to send command.\n"); + return HTInetStatus("send"); + } + + _HTProgress ("Gopher request sent; waiting for response."); + + /* + ** 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; + + case GOPHER_MACBINHEX: + case GOPHER_PCBINARY: + case GOPHER_UUENCODED: + case GOPHER_BINARY: + default: + /* + ** 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 PUBLIC HTProtocol HTGopher = { "gopher", HTLoadGopher, NULL }; +GLOBALDEF PUBLIC HTProtocol HTCSO = { "cso", HTLoadCSO, NULL }; +#endif /* GLOBALDEF_IS_MACRO */ |