/* 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" #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) /* 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) { START(HTML_TITLE); PUTS(arg); PUTS(" index"); END(HTML_TITLE); START(HTML_H1); PUTS(arg); PUTS(" index"); END(HTML_H1); START(HTML_ISINDEX); PUTS(GTR_GetString(SID_INF_GOPHER_ENTER_KEYWORDS)); FREE_TARGET; return; } /* Display a CSO index document ** ------------------------------- */ PRIVATE void display_cso(HTStructured *target, CONST char *arg) { START(HTML_TITLE); PUTS(arg); PUTS(" index"); END(HTML_TITLE); START(HTML_H1); PUTS(arg); PUTS(" index"); END(HTML_H1); START(HTML_ISINDEX); PUTS(GTR_GetString(SID_INF_CSO_ENTER_KEYWORDS)); 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; }; #define STATE_PARSE_GOTDATA (STATE_OTHER) PRIVATE int Gopher_ParseMenu_Async(struct Mwin *tw, int nState, void **ppInfo) { char gtype; signed char ch; char address[BIG]; char *name = ""; char *selector = ""; /* Gopher menu fields */ char *host = ""; char *port; char *pNext; struct Params_Gopher_ParseMenu *pParams; 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_GetString(SID_INF_GOPHER_SELECT_ONE_OF)); (*pParams->target->isa->start_element)(pParams->target, HTML_MENU, 0, 0); { struct Params_Isoc_Fill *pif; pif = GTR_CALLOC(sizeof(*pif), 1); if (pif) { pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } return STATE_PARSE_GOTDATA; case STATE_PARSE_GOTDATA: if (*pParams->pStatus > 0) { 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[(unsigned char) *p]) { *q++ = *p; } else { *q++ = HEX_ESCAPE; /* Means hex coming */ *q++ = hex[((unsigned char)*p) >> 4]; *q++ = hex[((unsigned char)*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); /* Get next block of data */ pif = GTR_CALLOC(sizeof(*pif), 1); if (pif) { pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); return STATE_PARSE_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } } (*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: (*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; } /*-----------------------------------------------------------------*/ 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; }; PRIVATE int Gopher_ParseCSO_Async(struct Mwin *tw, int nState, void **ppInfo) { signed char ch; char *pNext; struct Params_Gopher_ParseCSO *pParams; char *second_colon; 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_GetString(SID_INF_CSO_SEARCH_RESULTS)); (*pParams->target->isa->end_element)(pParams->target, HTML_H1); (*pParams->target->isa->start_element)(pParams->target, HTML_PRE, 0, 0); pParams->last_char = '\0'; { struct Params_Isoc_Fill *pif; pif = GTR_CALLOC(sizeof(*pif), 1); if (pif) { pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } return STATE_PARSE_GOTDATA; case STATE_PARSE_GOTDATA: if (*pParams->pStatus > 0) { 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 */ /* 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') 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); 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 <h2> */ /* 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 <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 */ (*pParams->target->isa->put_string)(pParams->target, second_colon + 1); (*pParams->target->isa->put_string)(pParams->target, "\n"); if (*(second_colon - 1) != pParams->last_char) /* end seperator */ { (*pParams->target->isa->end_element)(pParams->target, HTML_H2); (*pParams->target->isa->start_element)(pParams->target, HTML_PRE, 0, 0); } /* save the char before the second colon * for comparison on the next pass */ pParams->last_char = *(second_colon - 1); } /* end if second_colon */ } /* end if *p == '-' */ } } /* Loop over characters */ if (ch != EOF) { struct Params_Isoc_Fill *pif; /* Update display */ (*pParams->target->isa->block_done)(pParams->target); /* Get next block of data */ pif = GTR_CALLOC(sizeof(*pif), 1); if (pif) { pif->isoc = pParams->isoc; pif->pStatus = pParams->pStatus; Async_DoCall(Isoc_Fill_Async, pif); return STATE_PARSE_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } } /* end the text block */ (*pParams->target->isa->put_string)(pParams->target, "\n"); (*pParams->target->isa->end_element)(pParams->target, HTML_PRE); (*pParams->target->isa->put_string)(pParams->target, "\n"); (*pParams->target->isa->free)(pParams->target); HTInputSocket_free(pParams->isoc); return STATE_DONE; case STATE_ABORT: (*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; } struct Params_LoadGopher { HTRequest * request; int * pStatus; BOOL bWaiting; char * arg; char * command; char gtype; int net_status; struct MultiAddress address; unsigned short port; int s; char * pszHost; }; #define STATE_GOPHER_GOTHOST (STATE_OTHER + 0) #define STATE_GOPHER_CONNECTED (STATE_OTHER + 1) #define STATE_GOPHER_SENT (STATE_OTHER + 2) #define STATE_GOPHER_RECEIVED (STATE_OTHER + 3) #define STATE_GOPHER_GOTDATA (STATE_OTHER + 4) static void Gopher_CleanUp(struct Params_LoadGopher *pData) { XX_Assert(!pData->bWaiting, ("Gopher_DoCleanUp: WAIT stack not fully popped")); if (pData->s) { XX_DMsg(DBG_WWW, ("Gopher: close socket %d.\n", pData->s)); Net_Close(pData->s); } if (pData->command) GTR_FREE(pData->command); pData->request->content_length = 0; } static int Gopher_DoInit(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; struct Params_MultiParseInet * ppi; /* Copy the parameters we were passed into our own, larger structure. */ { struct Params_LoadAsync *pParams; pParams = *ppInfo; pData = GTR_CALLOC(sizeof(struct Params_LoadGopher), 1); if (pData) { pData->request = pParams->request; pData->pStatus = pParams->pStatus; GTR_FREE(pParams); *ppInfo = pData; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } pData->arg = pData->request->destination->szActualURL; if (!acceptable_inited) init_acceptable(); if (!pData->arg || !*pData->arg) { /* Illegal if no name or zero-length */ *pData->pStatus = -2; return STATE_DONE; } /* Set up defaults */ pData->port = WS_HTONS(GOPHER_PORT); /* Get node name and optional port number */ pData->pszHost = HTParse(pData->arg, "", PARSE_HOST); ppi = GTR_CALLOC(sizeof(*ppi), 1); if (ppi) { ppi->pAddress = &pData->address; ppi->pPort = &pData->port; ppi->str = pData->pszHost; ppi->pStatus = &pData->net_status; Async_DoCall(Net_MultiParse_Async, ppi); return STATE_GOPHER_GOTHOST; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } static int Gopher_DoGotHost(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; HTStructured *target; pData = *ppInfo; GTR_FREE(pData->pszHost); pData->pszHost = NULL; if (pData->net_status) { XX_DMsg(DBG_LOAD, ("Inet_Parse_Async returned %d\n", pData->net_status)); *pData->pStatus = -1; Gopher_CleanUp(pData); return STATE_DONE; } /* Get entity type, and selector string. */ { char *p; char *p1; char *selector; p1 = HTParse(pData->arg, "", PARSE_PATH | PARSE_PUNCTUATION); pData->gtype = '1'; /* Default = menu */ selector = p1; if ((*selector++ == '/') && (*selector)) { /* Skip first slash */ pData->gtype = *selector++; /* Pick up pData->gtype */ } if (pData->gtype == GOPHER_INDEX) { char *query; query = strchr(selector, '?'); /* Look for search string */ if (!query || !query[1]) { /* No search required */ target = HTML_new(tw, pData->request, NULL, WWW_HTML, pData->request->output_format, pData->request->output_stream); display_index(target, pData->arg); /* Display "cover page" */ GTR_FREE(p1); *pData->pStatus = HT_LOADED; /* We're done! */ Gopher_CleanUp(pData); return STATE_DONE; } *query++ = 0; /* Skip '?' */ pData->command = GTR_MALLOC(strlen(selector) + 1 + strlen(query) + 2 + 1); if (pData->command) { de_escape(pData->command, selector); /* Bug fix TBL 921208 */ strcat(pData->command, "\t"); { /* Remove plus signs 921006 */ char *p; for (p = query; *p; p++) { if (*p == '+') *p = ' '; } } strcat(pData->command, query); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } else if (pData->gtype == GOPHER_CSO) { char *query; query = strchr(selector, '?'); /* Look for search string */ if (!query || !query[1]) { /* No search required */ target = HTML_new(tw, pData->request, NULL, WWW_HTML, pData->request->output_format, pData->request->output_stream); display_cso(target, pData->arg); /* Display "cover page" */ GTR_FREE(p1); *pData->pStatus = HT_LOADED; /* We're done! */ Gopher_CleanUp(pData); return STATE_DONE; } *query++ = 0; /* Skip '?' */ pData->command = GTR_MALLOC(strlen("query") + 1 + strlen(query) + 2 + 1); if (pData->command) { de_escape(pData->command, selector); strcpy(pData->command, "query "); { /* Remove plus signs 921006 */ char *p; for (p = query; *p; p++) { if (*p == '+') *p = ' '; } } strcat(pData->command, query); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } else { /* Not index */ pData->command = pData->command = GTR_MALLOC(strlen(selector) + 2 + 1); if (pData->command) { de_escape(pData->command, selector); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } GTR_FREE(p1); p = pData->command + strlen(pData->command); *p++ = CR; *p++ = LF; *p++ = 0; } WAIT_Push(tw, waitPartialInteract, GTR_GetString(SID_INF_CONNECTING_TO_GOPHER_SERVER)); pData->bWaiting = TRUE; { /* Do connect call */ struct Params_MultiConnect *ppc; ppc = GTR_CALLOC(sizeof(*ppc), 1); if (ppc) { ppc->pSocket = &pData->s; ppc->pAddress = &pData->address; ppc->nPort = pData->port; ppc->pWhere = NULL; ppc->pStatus = &pData->net_status; #ifdef FEATURE_SOCKS_LOW_LEVEL ppc->bUseSocksProxy = pData->request->destination->bUseSocksProxy; #endif Async_DoCall(Net_MultiConnect_Async, ppc); } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } return STATE_GOPHER_CONNECTED; } static int Gopher_DoConnected(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; pData = *ppInfo; WAIT_Pop(tw); pData->bWaiting = FALSE; if (pData->net_status < 0) { XX_DMsg(DBG_LOAD, ("Unable to connect to remote host for %s (errno = %d)\n", pData->arg, errno)); *pData->pStatus = HTInetStatus("connect"); Gopher_CleanUp(pData); return STATE_DONE; } /* Do the send */ WAIT_Push(tw, waitPartialInteract, GTR_GetString(SID_INF_SENDING_GOPHER_COMMANDS)); pData->bWaiting = TRUE; { struct Params_Send *pps; pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { pps->socket = pData->s; pps->pBuf = pData->command; pps->nBufLen = strlen(pData->command); pps->pStatus = &pData->net_status; Async_DoCall(Net_Send_Async, pps); return STATE_GOPHER_SENT; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } } static int Gopher_DoSent(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; HTStructured * target; pData = *ppInfo; GTR_FREE(pData->command); pData->command = NULL; WAIT_Pop(tw); pData->bWaiting = FALSE; if (pData->net_status < 0) { XX_DMsg(DBG_LOAD, ("Unable to send to remote host for %s (errno = %d)\n", pData->arg, errno)); *pData->pStatus = HTInetStatus("send"); Gopher_CleanUp(pData); return STATE_DONE; } WAIT_Push(tw, waitPartialInteract, GTR_GetString(SID_INF_RECEIVING_GOPHER_DATA)); pData->bWaiting = TRUE; pData->net_status = 0; switch (pData->gtype) { case GOPHER_HTML: { struct Params_HTParseSocket *pps; pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { pps->format_in = WWW_HTML; pps->file_number = pData->s; pps->request = pData->request; pps->pStatus = &pData->net_status; Async_DoCall(HTParseSocket_Async, pps); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_GIF: case GOPHER_IMAGE: { struct Params_HTParseSocket *pps; pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { pps->format_in = HTAtom_for("image/gif"); pps->file_number = pData->s; pps->request = pData->request; pps->pStatus = &pData->net_status; Async_DoCall(HTParseSocket_Async, pps); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_MENU: case GOPHER_INDEX: target = HTML_new(tw, pData->request, NULL, WWW_HTML, pData->request->output_format, pData->request->output_stream); { struct Params_Gopher_ParseMenu *pgpm; pgpm = GTR_CALLOC(sizeof(*pgpm), 1); if (pgpm) { pgpm->target = target; pgpm->s = pData->s; pgpm->arg = pData->arg; pgpm->pStatus = &pData->net_status; Async_DoCall(Gopher_ParseMenu_Async, pgpm); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_CSO: target = HTML_new(tw, pData->request, NULL, WWW_HTML, pData->request->output_format, pData->request->output_stream); { struct Params_Gopher_ParseCSO *pgpc; pgpc = GTR_CALLOC(sizeof(*pgpc), 1); if (pgpc) { pgpc->target = target; pgpc->s = pData->s; pgpc->arg = pData->arg; pgpc->pStatus = &pData->net_status; Async_DoCall(Gopher_ParseCSO_Async, pgpc); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_MACBINHEX: case GOPHER_PCBINHEX: case GOPHER_UUENCODED: case GOPHER_BINARY: { struct Params_HTParseSocket *pps; HTFormat format; if (pData->gtype == GOPHER_MACBINHEX) { format = HTAtom_for("application/mac-binhex40"); pData->request->content_encoding = HTAtom_for("8bit"); } else if (pData->gtype == GOPHER_UUENCODED) { /* TODO: Is this the right MIME type? */ format = HTAtom_for("application/uuencoded"); pData->request->content_encoding = HTAtom_for("8bit"); } else format = HTFileFormat(pData->arg, &pData->request->content_encoding, &pData->request->content_language); pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { if (format) { XX_DMsg(DBG_WWW, ("Gopher...... figured out content-type myself: %s\n", HTAtom_name(format))); pps->format_in = format; } else { XX_DMsg(DBG_WWW, ("Gopher...... using www/unknown\n")); /* Specifying WWW_UNKNOWN forces dump to local disk. */ pps->format_in = WWW_UNKNOWN; } pps->file_number = pData->s; pps->request = pData->request; pps->pStatus = &pData->net_status; Async_DoCall(HTParseSocket_Async, pps); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_SOUND: { struct Params_HTParseSocket *pps; pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { pps->format_in = WWW_AUDIO; pps->file_number = pData->s; pps->request = pData->request; pps->pStatus = &pData->net_status; Async_DoCall(HTParseSocket_Async, pps); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } case GOPHER_TEXT: default: { struct Params_HTParseSocket *pps; /* TODO: Right now this doesn't take into account the fact that gopher terminates the transmission with "\r\n.\r\n" so an extra line with a period shows up at the end of the document. */ pps = GTR_CALLOC(sizeof(*pps), 1); if (pps) { pps->format_in = WWW_PLAINTEXT; pps->file_number = pData->s; pps->request = pData->request; pps->pStatus = &pData->net_status; Async_DoCall(HTParseSocket_Async, pps); return STATE_GOPHER_GOTDATA; } else { ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL); return STATE_ABORT; } } } /* switch(gtype) */ } static int Gopher_DoGotData(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; pData = *ppInfo; WAIT_Pop(tw); pData->bWaiting = FALSE; if (pData->net_status < 0) { XX_DMsg(DBG_LOAD, ("Unable to send to remote host for %s (errno = %d)\n", pData->arg, errno)); *pData->pStatus = HTInetStatus("recv"); Gopher_CleanUp(pData); return STATE_DONE; } Net_Close(pData->s); if (pData->net_status < 0) { *pData->pStatus = pData->net_status; } else { *pData->pStatus = HT_LOADED; } Gopher_CleanUp(pData); return STATE_DONE; } static int Gopher_DoAbort(struct Mwin *tw, void **ppInfo) { struct Params_LoadGopher *pData; pData = *ppInfo; *pData->pStatus = -1; if (pData->s) { XX_DMsg(DBG_WWW, ("Gopher: close socket %d.\n", pData->s)); Net_Close(pData->s); } if (pData->command) GTR_FREE(pData->command); if (pData->pszHost) GTR_FREE(pData->pszHost); if (pData->bWaiting) WAIT_Pop(tw); pData->request->content_length = 0; return STATE_DONE; } PRIVATE int HTLoadGopher_Async(struct Mwin *tw, int nState, void **ppInfo) { switch (nState) { case STATE_INIT: return Gopher_DoInit(tw, ppInfo); case STATE_GOPHER_GOTHOST: return Gopher_DoGotHost(tw, ppInfo); case STATE_GOPHER_CONNECTED: return Gopher_DoConnected(tw, ppInfo); case STATE_GOPHER_SENT: return Gopher_DoSent(tw, ppInfo); case STATE_GOPHER_GOTDATA: return Gopher_DoGotData(tw, ppInfo); case STATE_ABORT: return Gopher_DoAbort(tw, ppInfo); default: XX_Assert(0, ("HTLoadHTTP_Async: nState = %d\n", nState)); return STATE_DONE; } } GLOBALDEF PUBLIC HTProtocol HTGopher = {"gopher", NULL, HTLoadGopher_Async};