/* GOPHER ACCESS HTGopher.c ** ============= ** ** History: ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL ** 29 Nov 91 Downgraded to C, for portable implementation. ** 28 Apr 94 target no more global and icons implemented ** HF, frystyk@dxcern.cern.ch ** 2 May 94 Fixed possible security hole when the URL contains ** a newline, that could cause multiple commands to be ** sent to a Gopher server. AL, luotonen@www.cern.ch ** 12 May 94 Checked and made ready for multi-threads, Frystyk ** 8 Jul 94 FM Insulate free() from _free structure element. ** ** NOTE: ** When parsing a gopher menu, we can't use the default HTParseSocket ** but we will hav eto take care of the stram ourselves :-( ** */ /* Implementation dependent include files */ #include "tcp.h" /* Library include files */ #include "HTParse.h" #include "HTUtils.h" #include "HTTCP.h" #include "HTIcons.h" #include "HTAccess.h" #include "HTFormat.h" #include "HTError.h" #include "HTFile.h" #include "HTML.h" #include "HTMLPDTD.h" #include "HTDirBrw.h" #include "HTGopher.h" /* Implemented here */ /* Macros and other defines */ #ifndef GOPHER_PORT #define GOPHER_PORT 70 /* See protocol spec */ #endif /* Hypertext object building machinery */ #define PUTC(c) (*target->isa->put_character)(target, c) #define PUTS(s) (*target->isa->put_string)(target, s) #define START(e) (*target->isa->start_element)(target, e, 0, 0) #define END(e) (*target->isa->end_element)(target, e) #define FREE_TARGET (*target->isa->_free)(target) /* Type definitions and global variables etc. local to this module */ typedef enum _HTGopherType { GOPHER_TEXT = '0', GOPHER_MENU = '1', GOPHER_CSO = '2', GOPHER_ERROR = '3', GOPHER_MACBINHEX = '4', GOPHER_PCBINHEX = '5', GOPHER_UUENCODED = '6', GOPHER_INDEX = '7', GOPHER_TELNET = '8', GOPHER_BINARY = '9', GOPHER_GIF = 'g', GOPHER_HTML = 'h', /* HTML */ GOPHER_INFO = 'i', GOPHER_SOUND = 's', GOPHER_WWW = 'w', /* W3 address */ GOPHER_IMAGE = 'I', GOPHER_TN3270 = 'T', GOPHER_DUPLICATE = '+', GOPHER_PLUS_IMAGE = ':', /* Addition from Gopher Plus */ GOPHER_PLUS_MOVIE = ';', GOPHER_PLUS_SOUND = '<' } HTGopherType; struct _HTStructured { CONST HTStructuredClass * isa; /* ... */ }; /* This is the local definition of HTRequest->net_info */ typedef struct _gopher_info { int sockfd; /* Socket number for communication */ HTInputSocket * isoc; /* Input buffer */ int addressCount; /* Attempts if multi-homed */ BOOL CRLFdotCRLF; /* Transmission end like this */ HTRequest * request; /* Request structure */ HTGopherType type; /* Gopher item type */ } gopher_info; /* ------------------------------------------------------------------------- */ /* get_gopher_icon ** ** This function finds an appopriate icon for the item in the gopher ** list. Actually it is only a shell build upon HTGetIcon(). ** */ PRIVATE HTIconNode *get_gopher_icon ARGS2(CONST char *, filename, int, gopher_type) { HTFormat content_type = NULL; HTAtom *content_encoding = NULL; if (gopher_type == GOPHER_MENU) return icon_dir ? icon_dir : icon_unknown; switch(gopher_type) { case GOPHER_TEXT: content_type = HTAtom_for("text/void"); break; case GOPHER_IMAGE: case GOPHER_PLUS_IMAGE: case GOPHER_GIF: content_type = HTAtom_for("image/void"); break; case GOPHER_WWW: case GOPHER_HTML: content_type = HTAtom_for("text/void"); break; case GOPHER_SOUND: case GOPHER_PLUS_SOUND: content_type = HTAtom_for("audio/void"); break; case GOPHER_PLUS_MOVIE: content_type = HTAtom_for("video/void"); break; case GOPHER_INDEX: content_type = HTAtom_for("application/x-gopher-index"); break; case GOPHER_CSO: content_type = HTAtom_for("application/x-gopher-cso"); break; case GOPHER_TELNET: content_type = HTAtom_for("application/x-gopher-telnet"); break; case GOPHER_TN3270: content_type = HTAtom_for("application/x-gopher-tn3270"); break; case GOPHER_DUPLICATE: content_type = HTAtom_for("application/x-gopher-duplicate"); break; case GOPHER_ERROR: content_type = HTAtom_for("www/unknown"); break; case GOPHER_MACBINHEX: case GOPHER_PCBINHEX: case GOPHER_UUENCODED: case GOPHER_BINARY: { /* Do our own filetyping -- maybe we get lucky */ HTAtom *language; content_type = HTFileFormat(filename, &content_encoding, &language); } default: content_type = HTAtom_for("www/unknown"); break; } return HTGetIcon(S_IFMT & S_IFREG, content_type, content_encoding); } /* parse_menu ** ** This function parses a gopher menu and puts it into a iconized ** list. ** ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1 ** if other error. ** */ PRIVATE int parse_menu ARGS3(HTRequest *, request, gopher_info *, gopher, CONST char *, url) #define TAB '\t' #define HEX_ESCAPE '%' { int status = -1; unsigned int files = 0; int ch; HTChunk *chunk = HTChunkCreate(128); char *message = NULL; /* For a gopher message */ char *info = NULL; /* For gopher information send as `i' type */ HTStructured *target = NULL; gopher->isoc = HTInputSocket_new(gopher->sockfd); /* Output the list */ while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) { if (ch == CR || ch == LF) { if (chunk->size) { /* Got some text */ char *name = NULL; /* Gopher menu fields */ char *selector = NULL; char *host = NULL; char *port = NULL; char *strptr; char *errptr; char gtype; HTChunkTerminate(chunk); strptr = chunk->data; /* Scan it to parse it */ if (PROT_TRACE) fprintf(stderr, "HTGopher.... Menu item: `%s\'\n", chunk->data); gtype = *strptr++; #if 0 if (gtype == GOPHER_ERROR) { StrAllocCat(message, chunk->data+1); break; } #endif /* If information then add it to the info string */ if (gtype == GOPHER_INFO) { if ((errptr = strchr(chunk->data, '\t')) != NULL) *errptr = '\0'; if (info) { StrAllocCat(info, "\n"); StrAllocCat(info, chunk->data+1); } else StrAllocCopy(info, chunk->data+1); HTChunkClear(chunk); continue; } /* If first item is an error, then don't put any header out but wait and see if there is a next item in the list. If not then make error message, else use as list message. */ if (!files && (strstr(chunk->data, "error.host") || strstr(chunk->data, "errorhost"))) { /* If message is already initialized, then add this one. */ /* We don't want the gopher type character */ if ((errptr = strchr(chunk->data, '\t')) != NULL) *errptr = '\0'; if (message) { StrAllocCat(message, "\n"); StrAllocCat(message, chunk->data+1); } else StrAllocCopy(message, chunk->data+1); HTChunkClear(chunk); continue; } /* Stop listing if line with a dot by itself */ if (!files && message && gtype=='.' && !*strptr) { status = -1; break; } /* Output title, maybe top message and list top */ if (!files) { CONST char *title = HTAnchor_title(request->anchor); char *outstr = NULL; target = HTML_new(request, NULL, WWW_HTML, request->output_format, request->output_stream); if (title) { StrAllocCopy(outstr, title); HTUnEscape(outstr); } else StrAllocCopy(outstr, "Gopher Menu"); START(HTML_HTML); START(HTML_HEAD); START(HTML_TITLE); PUTS(outstr); END(HTML_TITLE); END(HTML_HEAD); START(HTML_BODY); START(HTML_H1); PUTS(outstr); END(HTML_H1); FREE(outstr); /* Output any message on top of list */ if ((message || info) && HTDirInfo == HT_DIR_INFO_TOP) { if (message) PUTS(message); if (info) PUTS(info); START(HTML_BR); } /* Make everything in list preformatted */ START(HTML_PRE); #ifdef OLD_CODE /* Output the header line of the list */ if (!icon_blank) icon_blank = icon_unknown; if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) { HTMLPutImg(target, icon_blank->icon_url, HTIcon_alt_string(icon_blank->icon_alt, NO), NULL); } PUTC(' '); PUTS("Name"); PUTC('\n'); #endif /* OLD_CODE */ START(HTML_HR); PUTC('\n'); } /* Stop listing if line with a dot by itself */ if (gtype=='.' && !*strptr) { status = (!files && message) ? -1 : HT_LOADED; break; } /* Parse menu item */ if (*strptr) { name = strptr; selector = strchr(name, TAB); if (selector) { *selector++ = 0; /* Terminate name */ host = strchr(selector, TAB); if (host) { *host++ = 0; /* Terminate selector */ port = strchr(host, TAB); if (port) { char *junk; *port = ':'; /* delimit host a la W3 */ if ((junk = strchr(port, TAB)) != NULL) *junk = '\0'; /* Chop port */ if (*(port+1) == '0' && !*(port+2)) *port = '\0'; } /* port */ } /* host */ } /* selector */ } /* gtype and name */ /* Get Icon type and output the icon */ if (HTDirShowMask & HT_DIR_SHOW_ICON) { HTIconNode *icon = get_gopher_icon(url, gtype); if (icon && icon->icon_url) { HTMLPutImg(target, icon->icon_url, HTIcon_alt_string(icon->icon_alt, YES), NULL); PUTC(' '); } } if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */ char *escaped = NULL; escaped = HTEscape(selector, URL_PATH); HTStartAnchor(target, NULL, escaped); PUTS(name); END(HTML_A); free(escaped); } else if (port) { /* Other types need port */ char *escaped = NULL; char *address = NULL; int addr_len; /* Calculate the length of the WWW-address */ if (selector && *selector) { escaped = HTEscape(selector, URL_PATH); addr_len = 15 + strlen(escaped) + strlen(host) + 1; } else { addr_len = 15 + strlen(host) + 1; } if ((address = (char *) malloc(addr_len)) == NULL) outofmem(__FILE__, "Gopher ParseMenu"); *address = '\0'; if (gtype == GOPHER_TELNET) { if (escaped) sprintf(address, "telnet://%s@%s/", escaped, host); else sprintf(address, "telnet://%s/", host); } else if (gtype == GOPHER_TN3270) { if (escaped) sprintf(address, "tn3270://%s@%s/", escaped, host); else sprintf(address, "tn3270://%s/", host); } else { if (escaped) sprintf(address, "//%s/%c%s", host, gtype, escaped); else sprintf(address, "//%s/%c", host, gtype); } /* Now output the anchor if not a Gopher error */ if (gtype != GOPHER_ERROR && !strstr(address, "error.host") && !strstr(address, "errorhost")) { HTStartAnchor(target, NULL, address); PUTS(name); END(HTML_A); } else PUTS(name); /* Just put it out, but skip type */ FREE(address); FREE(escaped); } else { /* If parse error */ if (PROT_TRACE) fprintf(stderr, "HTGopher.... Bad menu item, `%s\'\n", chunk->data); PUTS(chunk->data); } PUTC('\n'); HTChunkClear(chunk); ++files; /* Update number of files */ } } else HTChunkPutc(chunk, ch); } if (ch < 0) status = ch; /* If no files and message is initialized then make error message, else output the bottom part of the list*/ if (status != HT_INTERRUPTED) { if (!files && status < 0) { if (message) { HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER, (void *) message, strlen(message), "parse_menu"); } else { HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER, chunk->data, chunk->size, "parse_menu"); } } else if (target) { #ifdef OLD_CODE char *outstr; if ((outstr = (char *) malloc(100)) == NULL) outofmem(__FILE__, "parse_menu"); if (files == 0) sprintf(outstr, "Empty directory"); else if (files == 1) sprintf(outstr, "1 file"); else sprintf(outstr, "%u files", files); START(HTML_HR); PUTS(outstr); free(outstr); #endif /* OLD_CODE */ START(HTML_HR); if (!files) PUTS("Empty Gopher Menu"); END(HTML_PRE); /* Put out any messages */ if ((message || info) && HTDirInfo == HT_DIR_INFO_BOTTOM) { if (message) PUTS(message); if (info) PUTS(info); START(HTML_BR); } END(HTML_BODY); END(HTML_HTML); FREE_TARGET; } else { if (PROT_TRACE) fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n"); } } /* Cleanup */ FREE(message); FREE(info); HTInputSocket_free(gopher->isoc); HTChunkFree(chunk); return status; } /* Parse a Gopher CSO document ** ============================ ** ** 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. ** ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1 ** if other error. */ PRIVATE int parse_cso ARGS3(HTRequest *, request, gopher_info *, gopher, CONST char *, url) { int status = -1; unsigned int records = 0; int ch; char *cur_code = NULL; HTChunk *chunk = HTChunkCreate(128); HTStructured *target = NULL; gopher->isoc = HTInputSocket_new(gopher->sockfd); /* Start grabbing chars from the network */ while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) { if (ch == CR || ch == LF) { if (chunk->size) { /* OK we now have a line in 'p' lets parse it and print it */ char *strptr; HTChunkTerminate(chunk); strptr = chunk->data; /* If line begins with a 1, then more data is coming, so we put out the title */ if (*strptr == '1' || !strncmp(strptr, "501", 3) || !strncmp(strptr, "502", 3)) { /* Put up new stream */ target = HTML_new(request, NULL, WWW_HTML, request->output_format, request->output_stream); START(HTML_H1); PUTS("CSO Search Results"); END(HTML_H1); /* Output the header line of the list */ START(HTML_PRE); /* To make it look as the other headers */ if (!icon_blank) icon_blank = icon_unknown; if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) { HTMLPutImg(target, icon_blank->icon_url, HTIcon_alt_string(icon_blank->icon_alt, NO), NULL); } PUTC(' '); PUTS("Record"); PUTC('\n'); START(HTML_HR); PUTC('\n'); END(HTML_PRE); } /* Break on line that begins with a 2. It's the end of data. */ if (*strptr == '2') { status = HT_LOADED; break; } /* Lines beginning with 5 are errors, generate msg and quit */ if (*strptr == '5') { char *msgptr = strchr(chunk->data, ':'); if (!msgptr) msgptr = chunk->data; else ++msgptr; if (!strncmp(strptr, "501", 3)) /* No entries */ status = HT_LOADED; else if (!strncmp(strptr, "502", 3)) { /* Too many */ status = HT_LOADED; PUTS(msgptr); } else { HTErrorAdd(request, ERR_FATAL, NO, HTERR_CSO_SERVER, (void *) msgptr, strlen(msgptr), "parse_cso"); } break; } if(*strptr == '-') { /* data lines look like -200:#: * where # is the search result number and can be * multiple digits (infinate?) * 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