/* Enhanced NCSA Mosaic from Spyglass "Guitar" Copyright 1994 Spyglass, Inc. All Rights Reserved Author(s): Jim Seidman jim@spyglass.com Portions of this file were derived from the CERN libwww, version 2.15. */ #include "all.h" #include "history.h" #define GOPHER_PORT 70 /* 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_PCBINHEX '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_SOUND 's' #define GOPHER_WWW 'w' /* W3 address */ #define GOPHER_IMAGE 'I' #define GOPHER_TN3270 'T' #define GOPHER_DUPLICATE '+' #define TAB '\t' #define HEX_ESCAPE '%' #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) struct _HTStructured { CONST HTStructuredClass *isa; /* ... */ }; #define GOPHER_PROGRESS(foo) HTAlert(foo) PRIVATE int Gopher_ParseMenu_Async(struct Mwin *tw, int nState, void **ppInfo); PRIVATE int Gopher_ParseCSO_Async(struct Mwin *tw, int nState, void **ppInfo); /* Module-wide variables */ PRIVATE int s; /* Socket for GopherHost */ /* Matrix of allowed characters in filenames ** ----------------------------------------- */ PRIVATE BOOL acceptable[256]; PRIVATE BOOL acceptable_inited = FALSE; PRIVATE void init_acceptable(void) { unsigned int i; char *good = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$"; for (i = 0; i < 256; i++) acceptable[i] = NO; for (; *good; good++) acceptable[(unsigned int) *good] = YES; acceptable_inited = YES; } PRIVATE CONST char hex[17] = "0123456789abcdef"; /* Decdoe one hex character */ PRIVATE char from_hex(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 ** ------------------ ** ** 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. */ PRIVATE void write_anchor(HTStructured *target, 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; value[HTML_A_HREF] = addr; present[HTML_A_TITLE] = YES; value[HTML_A_TITLE] = text; (*target->isa->start_element) (target, HTML_A, present, value); PUTS(text); END(HTML_A); } /* Display a Gopher Index document ** ------------------------------- */ PRIVATE void display_index(HTStructured *target, CONST char *arg) { char szBuf[128]; START(HTML_TITLE); PUTS(arg); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER1,szBuf,sizeof(szBuf))); END(HTML_TITLE); START(HTML_H1); PUTS(arg); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER1,szBuf,sizeof(szBuf))); END(HTML_H1); START(HTML_ISINDEX); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER2,szBuf,sizeof(szBuf))); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER3,szBuf,sizeof(szBuf))); FREE_TARGET; return; } /* Display a CSO index document ** ------------------------------- */ PRIVATE void display_cso(HTStructured *target, CONST char *arg) { char szBuf[128]; START(HTML_TITLE); PUTS(arg); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER1,szBuf,sizeof(szBuf))); END(HTML_TITLE); START(HTML_H1); PUTS(arg); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER1,szBuf,sizeof(szBuf))); END(HTML_H1); START(HTML_ISINDEX); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER4,szBuf,sizeof(szBuf))); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER5,szBuf,sizeof(szBuf))); PUTS(GTR_formatmsg(RES_STRING_HTGOPHER6,szBuf,sizeof(szBuf))); FREE_TARGET; return; } /* De-escape a selector into a command ** ----------------------------------- ** ** The % hex escapes are converted. Otheriwse, the string is copied. */ PRIVATE void de_escape(char *command, CONST char *selector) { CONST char *p = selector; char *q = command; 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++ = (b << 4) + from_hex(c); } else { *q++ = *p++; /* Record */ } } *q++ = 0; /* Terminate command */ } /*****************************************************************/ /* Async stuff /*****************************************************************/ struct Params_Gopher_ParseMenu { HTStructured * target; int s; CONST char * arg; int * pStatus; /* Status return */ /* Used internally */ char line[BIG]; HTInputSocket * isoc; char * p; BOOL fFromDCache; FILE * fpDc; char * pszDcFile; }; struct Params_Gopher_ParseCSO { HTStructured * target; int s; CONST char * arg; int * pStatus; /* Status return */ /* Used internally */ char line[BIG]; HTInputSocket * isoc; char * p; char last_char; BOOL fFromDCache; FILE * fpDc; char * pszDcFile; }; const char cszGopherMenu[]="gopher/menu"; const char cszGopherCSO[]="gopher/CSO"; #define HTGopherMenuFormat() HTAtom_for(cszGopherMenu) #define HTGopherCSOFormat() HTAtom_for(cszGopherCSO) static BOOL FGopherMenuFormat(HTFormat format) { return (format == HTAtom_for(cszGopherMenu)); } static BOOL FGopherCSOFormat(HTFormat format) { return (format == HTAtom_for(cszGopherCSO)); } BOOL FGopherFormat(HTFormat format) { return ( FGopherMenuFormat(format) || FGopherCSOFormat(format)); } int DoGopherDCache(struct Mwin *tw, struct Data_LoadFileCache *pData, HTFormat format) { HTStructured * target = NULL; int state; pData->request = HTRequest_validate(pData->request); if (tw == NULL || pData->request == NULL) goto LErr; target = HTML_new(tw, pData->request, NULL, WWW_HTML, pData->request->output_format, pData->request->output_stream); if (FGopherMenuFormat(format)) { struct Params_Gopher_ParseMenu *pgpm; if (!(pgpm = GTR_MALLOC(sizeof(*pgpm)))) goto LErr; pgpm->fFromDCache = TRUE; pgpm->fpDc = pData->fp; pgpm->target = target; pgpm->s = 0; pgpm->arg = pData->request->destination->szActualURL; pgpm->pStatus = pData->pStatus; state = Gopher_ParseMenu_Async(tw, STATE_INIT, &pgpm); GTR_FREE(pgpm); } else { struct Params_Gopher_ParseCSO *pgpc; XX_Assert(FGopherCSOFormat(format), ("")); if (!(pgpc = GTR_MALLOC(sizeof(*pgpc)))) goto LErr; pgpc->fFromDCache = TRUE; pgpc->fpDc = pData->fp; pgpc->target = target; pgpc->s = 0; pgpc->arg = pData->request->destination->szActualURL; pgpc->pStatus = pData->pStatus; state = Gopher_ParseCSO_Async(tw, STATE_INIT, &pgpc); GTR_FREE(pgpc); } if (*pData->pStatus == HT_LOADED) { // TW_AddToHistory(tw, pData->request->destination->szActualURL); GHist_Add(pData->request->destination->szActualURL, NULL, time(NULL), TRUE); if (tw->w3doc) W3Doc_CheckAnchorVisitations(tw->w3doc, tw); // UpdateHistoryMenus(tw); } return state; LErr: if (target) (*target->isa->free)(target); *pData->pStatus = -1; return STATE_DONE; } #define STATE_PARSE_GOTDATA (STATE_OTHER) PRIVATE int Gopher_ParseMenu_Async(struct Mwin *tw, int nState, void **ppInfo) { char gtype; char ch; char address[BIG]; char *name = ""; char *selector = ""; /* Gopher menu fields */ char *host = ""; char *port; char *pNext; struct Params_Gopher_ParseMenu *pParams; char szBuf[128]; int cb; /*count of bytes read in from dcache */ DCACHETIME dctNever = {DCACHETIME_EXPIRE_NEVER,DCACHETIME_EXPIRE_NEVER}; DCACHETIME dct = {0,0}; pParams = *ppInfo; switch (nState) { case STATE_INIT: pParams->isoc = HTInputSocket_new(pParams->s); pParams->p = pParams->line; (*pParams->target->isa->put_string)(pParams->target, GTR_formatmsg(RES_STRING_HTGOPHER7,szBuf,sizeof(szBuf))); (*pParams->target->isa->start_element)(pParams->target, HTML_MENU, 0, 0); if (!pParams->fFromDCache) { struct Params_Isoc_Fill *pif; #ifdef FEATURE_INTL SetFileDCache(tw->w3doc, pParams->arg, ENCODING_BINARY, &pParams->fpDc, &pParams->pszDcFile, HTGopherMenuFormat()); #else SetFileDCache(pParams->arg, ENCODING_BINARY, &pParams->fpDc, &pParams->pszDcFile, HTGopherMenuFormat()); #endif pif = GTR_MALLOC(sizeof(*pif)); pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); return STATE_PARSE_GOTDATA; } else goto LDataFromDCache; case STATE_PARSE_GOTDATA: if (*pParams->pStatus > 0) { LDataFromDCache: while (1) { if (!pParams->fFromDCache) { /* Getting data from the net */ if (pParams->fpDc) { /* save it to dcache */ fwrite( pParams->isoc->input_buffer, 1, pParams->isoc->input_limit - pParams->isoc->input_buffer, pParams->fpDc); } } else { /* getting data from dcache, read it */ XX_Assert(pParams->fpDc, ("")); cb = fread(pParams->isoc->input_buffer, 1, BIG - 1, pParams->fpDc); if (cb == 0) { /* error */ if (ferror(pParams->fpDc) != 0) goto LState_Abort; /* EOF: break out of while loop */ break; } pParams->isoc->input_limit = pParams->isoc->input_buffer + cb; } for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++) { ch = *pNext; if (ch == EOF) { break; } else if (ch == CR) { continue; } else if (ch != LF) { *pParams->p = ch; /* Put character in line */ if (pParams->p < &pParams->line[BIG - 1]) pParams->p++; } else { *pParams->p++ = 0; /* Terminate line */ pParams->p = pParams->line; /* Scan it to parse it */ port = 0; /* Flag "not parsed" */ XX_DMsg(DBG_WWW, ("HTGopher: Menu item: %s\n", pParams->line)); gtype = *pParams->p++; /* Break on line with a dot by itself */ if ((gtype == '.') && ((*pParams->p == CR) || (*pParams->p == 0))) break; if (gtype && *pParams->p) { name = pParams->p; selector = strchr(name, TAB); (*pParams->target->isa->start_element)(pParams->target, HTML_LI, 0, 0); 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[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 */ if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */ write_anchor(pParams->target, name, selector); } else if (port) { /* Other types need port */ if (gtype == GOPHER_TELNET) { if (*selector) sprintf(address, "telnet://%s@%s/", selector, host); else sprintf(address, "telnet://%s/", host); } else if (gtype == GOPHER_TN3270) { if (*selector) sprintf(address, "tn3270://%s@%s/", selector, host); else sprintf(address, "tn3270://%s/", host); } else { /* If parsed ok */ char *q; char *p; sprintf(address, "//%s/%c", host, gtype); q = address + strlen(address); for (p = selector; *p; p++) { /* Encode selector string */ if (acceptable[(int) *p]) *q++ = *p; else { *q++ = HEX_ESCAPE; /* Means hex coming */ *q++ = hex[(*p) >> 4]; *q++ = hex[(*p) & 15]; } } *q++ = 0; /* terminate address */ } (*pParams->target->isa->put_string)(pParams->target, " "); /* Error response from Gopher doesn't deserve to be a hyperlink. */ if (strcmp(address, "gopher://error.host:1/0")) write_anchor(pParams->target, name, address); else (*pParams->target->isa->put_string)(pParams->target, name); (*pParams->target->isa->put_string)(pParams->target, "\n"); } else { /* parse error */ XX_DMsg(DBG_WWW, ("HTGopher: Bad menu item.\n")); (*pParams->target->isa->put_string)(pParams->target, pParams->line); } /* parse error */ pParams->p = pParams->line; /* Start again at beginning of line */ } /* if end of line */ } /* Loop over characters */ if (ch != EOF) { struct Params_Isoc_Fill *pif; /* Update display */ (*pParams->target->isa->block_done)(pParams->target); if (!pParams->fFromDCache) { /* Get next block of data from net */ pif = GTR_MALLOC(sizeof(*pif)); pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); return STATE_PARSE_GOTDATA; } } else /* ch == EOF */ break; } /* end while (1) */ } /* if (*pParams->pStatus > 0) */ if (!pParams->fFromDCache) { UpdateFileDCache( pParams->arg, /* szActualURL */ &pParams->fpDc, &pParams->pszDcFile, HTGopherMenuFormat(), dctNever, dct, /*fAbort=*/FALSE, FALSE, tw); } else *pParams->pStatus = HT_LOADED; (*pParams->target->isa->end_element)(pParams->target, HTML_MENU); (*pParams->target->isa->free)(pParams->target); HTInputSocket_free(pParams->isoc); return STATE_DONE; case STATE_ABORT: LState_Abort: if (!pParams->fFromDCache) { UpdateFileDCache( pParams->arg, /* szActualURL */ &pParams->fpDc, &pParams->pszDcFile, HTGopherMenuFormat(), dctNever, dct, /*fAbort=*/TRUE, FALSE, tw); } else *pParams->pStatus = -1; (*pParams->target->isa->abort)(pParams->target, HTERROR_CANCELLED); HTInputSocket_free(pParams->isoc); return STATE_DONE; } XX_Assert((0), ("Function called with illegal state: %d", nState)); return STATE_DONE; } /*-----------------------------------------------------------------*/ PRIVATE int Gopher_ParseCSO_Async(struct Mwin *tw, int nState, void **ppInfo) { char ch; char *pNext; struct Params_Gopher_ParseCSO *pParams; char *second_colon; char szBuf[128]; int cb; /*count of bytes read in from dcache */ DCACHETIME dctNever = {DCACHETIME_EXPIRE_NEVER,DCACHETIME_EXPIRE_NEVER}; DCACHETIME dct = {0,0}; pParams = *ppInfo; switch (nState) { case STATE_INIT: pParams->isoc = HTInputSocket_new(pParams->s); pParams->p = pParams->line; (*pParams->target->isa->start_element)(pParams->target, HTML_H1, 0, 0); (*pParams->target->isa->put_string)(pParams->target, GTR_formatmsg(RES_STRING_HTGOPHER8,szBuf,sizeof(szBuf))); (*pParams->target->isa->end_element)(pParams->target, HTML_H1); (*pParams->target->isa->start_element)(pParams->target, HTML_PRE, 0, 0); pParams->last_char = '\0'; if (!pParams->fFromDCache) { struct Params_Isoc_Fill *pif; #ifdef FEATURE_INTL SetFileDCache(tw->w3doc, pParams->arg, ENCODING_BINARY, &pParams->fpDc, &pParams->pszDcFile, HTGopherCSOFormat()); #else SetFileDCache(pParams->arg, ENCODING_BINARY, &pParams->fpDc, &pParams->pszDcFile, HTGopherCSOFormat()); #endif pif = GTR_MALLOC(sizeof(*pif)); pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); return STATE_PARSE_GOTDATA; } else goto LDataFromDCache; case STATE_PARSE_GOTDATA: if (*pParams->pStatus > 0) { LDataFromDCache: while (1) { if (!pParams->fFromDCache) { /* Getting data from the net */ if (pParams->fpDc) { /* save it to dcache */ fwrite( pParams->isoc->input_buffer, 1, pParams->isoc->input_limit - pParams->isoc->input_buffer, pParams->fpDc); } } else { /* getting data from dcache, read it */ XX_Assert(pParams->fpDc, ("")); cb = fread(pParams->isoc->input_buffer, 1, BIG - 1, pParams->fpDc); if (cb == 0) { /* error */ if (ferror(pParams->fpDc) != 0) goto LState_Abort; /* EOF: break out of while loop */ break; } pParams->isoc->input_limit = pParams->isoc->input_buffer + cb; } for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++) { ch = *pNext; if (ch == EOF) { break; } else if (ch == CR) { /* * Lines starting with 2 or 5 cause us to break out */ if (pParams->p == pParams->line || (*pParams->line != '2' && *pParams->line != '5')) continue; ch = LF; } if (ch != LF) { *pParams->p = ch; /* Put character in line */ if (pParams->p < &pParams->line[BIG - 1]) pParams->p++; } else { *pParams->p++ = 0; /* Terminate line */ pParams->p = pParams->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 (*pParams->p == '2') { ch = EOF; break; } /* lines beginning with 5 are errors, * print them and quit */ if (*pParams->p == '5') { (*pParams->target->isa->start_element)(pParams->target, HTML_H2, 0, 0); (*pParams->target->isa->put_string)(pParams->target, pParams->p + 4); (*pParams->target->isa->end_element)(pParams->target, HTML_H2); ch = EOF; break; } if (*pParams->p == '-') { /* 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

*/ /* find the second_colon */ second_colon = strchr(strchr(pParams->p, ':') + 1, ':'); if (second_colon != NULL) { /* error check */ if (*(second_colon - 1) != pParams->last_char) /* print seperator */ { (*pParams->target->isa->end_element)(pParams->target, HTML_PRE); (*pParams->target->isa->start_element)(pParams->target, HTML_H2, 0, 0); } /* right now the record appears with the alias * (first line) * as the header and the rest as
 text
								 * It might look better with the name as the
								 * header and the rest as a