/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* A state machine to implement the Gopher protocol * Designed and Implemented by Lou Montulli circa '94 */ #include "rosetta.h" #include "xp.h" #include "plstr.h" #include "prmem.h" #include "netutils.h" #include "mkselect.h" #include "mktcp.h" #include "prerror.h" /* Gopher types: */ #define GTYPE_TEXT '0' /* text/plain */ #define GTYPE_MENU '1' /* menu - becomes text/html */ #define GTYPE_CSO '2' /* cso search - becomes text/html */ #define GTYPE_ERROR '3' /* some sort of error */ #define GTYPE_MACBINHEX '4' /* application/binhex */ #define GTYPE_PCBINARY '5' /* application/octet-stream */ #define GTYPE_UUENCODED '6' /* application/uuencoded */ #define GTYPE_INDEX '7' /* search - becomes text/html */ #define GTYPE_TELNET '8' /* maps to telnet URL */ #define GTYPE_BINARY '9' /* application/octet-stream */ #define GTYPE_GIF 'g' /* image/gif */ #define GTYPE_HTML 'h' /* HTML URL */ #define GTYPE_HTMLCAPS 'H' /* HTML URL Capitalized */ #define GTYPE_INFO 'i' /* unselectable info */ #define GTYPE_IMAGE 'I' /* image/gif */ #define GTYPE_MIME 'm' /* not used */ #define GTYPE_SOUND 's' /* audio/(wildcard) */ #define GTYPE_TN3270 'T' /* maps to tn3270 program (url) */ #define GTYPE_WWW 'w' /* W3 address */ #define GTYPE_MPEG ';' /* video/mpeg */ #include "mkgeturl.h" /* URL Struct */ #include "mkstream.h" #include "mkformat.h" /* for File Format stuff (cinfo) */ #include "gophurl.h" /* function prototypes */ #include "mktcp.h" /* connect, read, etc. */ #include "mkparse.h" /* NET_ParseURL() */ #include "remoturl.h" /* NET_RemoteHostLoad */ /* for XP_GetString() */ #include "xpgetstr.h" extern int XP_HTML_GOPHER_INDEX; extern int XP_HTML_CSO_SEARCH; extern int XP_PROGRESS_WAITREPLY_GOTHER; extern int MK_OUT_OF_MEMORY; extern int MK_SERVER_DISCONNECTED; extern int MK_TCP_READ_ERROR; extern int MK_TCP_WRITE_ERROR; extern int XP_ERRNO_EWOULDBLOCK; extern int XP_SERVER_RETURNED_NO_DATA; extern int MK_MALFORMED_URL_ERROR; #include "merrors.h" #define CD_OUTPUT_BUFFER_SIZE 4*1024 typedef struct _GopherConData { int next_state; /* the next state or action to be taken */ char *data_buf; /* temporary string storage */ int32 data_buf_size; /* current size of the line buffer */ char gopher_type; /* the gopher type */ char *command; /* the request command */ char *output_buf; /* temporary output buffer */ NET_StreamClass *stream; /* The output stream */ Bool pause_for_read; /* Pause now for next read? */ Bool destroy_graph_progress; /* do we need to destroy graph progress? */ int32 original_content_length; /* the content length at the time of * calling graph progress */ char cso_last_char; TCP_ConData *tcp_con_data; } GopherConData; #define PUTSTRING(s) (*connection_data->stream->put_block) \ (connection_data->stream, s, PL_strlen(s)) #define PUTBLOCK(b, l) (*connection_data->stream->put_block) \ (connection_data->stream, b, l) #define COMPLETE_STREAM (*connection_data->stream->complete) \ (connection_data->stream) #define ABORT_STREAM(s) (*connection_data->stream->abort) \ (connection_data->stream, s) /* helpful shortcut names */ #define CD_NEXT_STATE connection_data->next_state #define CD_DATA_BUF connection_data->data_buf #define CD_DATA_BUF_SIZE connection_data->data_buf_size #define CD_STREAM connection_data->stream #define CD_GOPHER_TYPE connection_data->gopher_type #define CD_COMMAND connection_data->command #define CD_OUTPUT_BUF connection_data->output_buf #define CD_PAUSE_FOR_READ connection_data->pause_for_read #define CD_DESTROY_GRAPH_PROGRESS connection_data->destroy_graph_progress #define CD_ORIGINAL_CONTENT_LENGTH connection_data->original_content_length #define CD_CSO_LAST_CHAR connection_data->cso_last_char #define CD_TCP_CON_DATA connection_data->tcp_con_data #define CE_WINDOW_ID cur_entry->window_id #define CE_URL_S cur_entry->URL_s #define CE_STATUS cur_entry->status #define CE_SOCK cur_entry->socket #define CE_CON_SOCK cur_entry->con_sock #define CE_BYTES_RECEIVED cur_entry->bytes_received /* states for the state machine */ #define GOPHER_BEGIN_CONNECT 1 #define GOPHER_FINISH_CONNECT 2 #define GOPHER_SEND_REQUEST 3 #define GOPHER_BEGIN_TRANSFER 4 #define GOPHER_TRANSFER_MENU 5 #define GOPHER_TRANSFER_CSO 6 #define GOPHER_TRANSFER_BINARY 7 #define GOPHER_DONE 8 #define GOPHER_ERROR_DONE 9 #define GOPHER_FREE 10 /* forward decl */ PRIVATE int32 net_ProcessGopher(ActiveEntry * cur_entry); /* net_begin_gopher_menu * * adds little bits to the begining of the document so that is looks nice */ PRIVATE int net_begin_gopher_menu(GopherConData * connection_data) { CD_NEXT_STATE = GOPHER_TRANSFER_MENU; PL_strcpy(CD_OUTPUT_BUF, "

Gopher Menu

\n
");
   	return(PUTSTRING(CD_OUTPUT_BUF));

}

/* parse the gopher menu type
 */
PRIVATE int 
net_parse_menu (ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;
    char gopher_type;
    char *name=NULL;
	char *gopher_path=0; 
    char *port;         
    char *ptr;
    char *line;
    char *host=NULL;        

    CE_STATUS = NET_BufferedReadLine(CE_SOCK, &line, &CD_DATA_BUF,
                                                &CD_DATA_BUF_SIZE, &CD_PAUSE_FOR_READ);

    if(CE_STATUS == 0)
	  {
        CD_NEXT_STATE = GOPHER_DONE;
        CD_PAUSE_FOR_READ = FALSE;
		if(CE_BYTES_RECEIVED < 4)
		  {
			PL_strcpy(CD_OUTPUT_BUF, XP_GetString( XP_SERVER_RETURNED_NO_DATA ) );
        	PUTSTRING(CD_OUTPUT_BUF);
		  }

       	return(MK_DATA_LOADED);
	  }

    /* if TCP error of if there is not a full line yet return
     */
    if(!line)
      {
         return CE_STATUS;
      }
    else if(CE_STATUS < 0)
      {
        NET_ExplainErrorDetails(MK_TCP_READ_ERROR, PR_GetOSError());

        /* return TCP error
         */
        return MK_TCP_READ_ERROR;
      }

	if(CE_STATUS > 1)
	  {
		CE_BYTES_RECEIVED += CE_STATUS;
        FE_GraphProgress(CE_WINDOW_ID, CE_URL_S, CE_BYTES_RECEIVED, CE_STATUS, CE_URL_S->content_length);
	  }

    gopher_type = *line;

	if(gopher_type != '\0')
        ptr = line+1;
	else
		return(0);

    /* remove trailing spaces */
    XP_StripLine(line);

    TRACEMSG(("gopher_parse_menu: parsing line: %s\n",line));

    /* quit when just a dot is found on a line by itself
	 */
    if(!PL_strcmp(line,"."))
      {
        CD_NEXT_STATE = GOPHER_DONE;
        CD_PAUSE_FOR_READ = FALSE;

		if(CE_BYTES_RECEIVED < 4)
		  {
			PL_strcpy(CD_OUTPUT_BUF, XP_GetString( XP_SERVER_RETURNED_NO_DATA ) );
        	PUTSTRING(CD_OUTPUT_BUF);
		  }
			
        return(MK_DATA_LOADED);
      }

    if (gopher_type && *ptr) 
	  {
        name = ptr;
        gopher_path = PL_strchr(name, '\t');
        if (gopher_path) 
		  {
            *gopher_path++ = 0;
            host = PL_strchr(gopher_path, '\t');
            if (host) 
			  {
                *host++ = 0;    
                port = PL_strchr(host, '\t');
                if (port) 
				  {
                    char *tab;

                    port[0] = ':';    /* fake the port no */
                    tab = PL_strchr(port, '\t');
                    if (tab) 
						*tab++ = '\0';  

                    if (port[1]=='0' && port[2]=='\0')
                        port[0] = 0;    
                } 
             } 
        } 
    } 

	if(!gopher_path)
	  {
        return(PUTSTRING(ptr)); /* keep going bad data */
	  }
        
    /* Nameless files are a separator line */
    if (gopher_type == GTYPE_TEXT) 
      {
        int i=0;
        while(NET_IS_SPACE(name[i])) i++;
        if(!PL_strlen(name))
        gopher_type = GTYPE_INFO;
      }

    /* these first few are grouped together with an
     * if else because they each have special needs 
     * the rest of them fit nicely in the switch
     */
	if(gopher_type == GTYPE_ERROR)
	  {
		/* ignore it */
	  }
    else if(gopher_type == GTYPE_WWW)
      {
        /* points to URL */
		PL_strcpy(CD_OUTPUT_BUF, "");
        CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);

		if(CE_STATUS < 0)
			return(CE_STATUS);

    	PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "%s\n", gopher_path, name);
    	return(PUTSTRING(CD_OUTPUT_BUF));

      }
    else if(gopher_type == GTYPE_INFO)
      {
            /* Information or separator line */
		PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "       %s\n", name);
        CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
       }
    else if(gopher_type == GTYPE_TELNET)
      {
        if (*gopher_path) 
		  {
			char * temp = NET_Escape(gopher_path, URL_XALPHAS);
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, " %s\n", temp, host, name);
			PR_Free(temp);
		  }
        else 
		  {
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, " %s\n", host, name);
		  }
    	CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
      }
    else if(gopher_type == GTYPE_TN3270)
      {
        if (gopher_path && *gopher_path) 
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, " %s\n", gopher_path, host, name);
        else 
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, " %s\n", host, name);
    	CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
      }
    else
      {
        if(host && gopher_path) 
	 	  {
			if(!PL_strcmp(host, "error.host:70"))
			  {
				
                PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "Error: %s", name);
                CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
				
			  }
			else
			  {
			    char * newpath = NET_Escape(gopher_path, URL_PATH);
    
                PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "", host, gopher_type, newpath);
    
                switch(gopher_type) {
                    case GTYPE_TEXT:
        		    case GTYPE_HTML:
        		    case GTYPE_HTMLCAPS:
            		  PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
									    "");
                            break;
                    case GTYPE_MENU:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                            break;
                    case GTYPE_CSO:
                    case GTYPE_INDEX:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                            break;
                    case GTYPE_PCBINARY:
                    case GTYPE_UUENCODED:
                    case GTYPE_BINARY:
                    case GTYPE_MACBINHEX:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                            break;
                    case GTYPE_IMAGE:
                    case GTYPE_GIF:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                            break;
                    case GTYPE_SOUND:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                            break;
        		    case GTYPE_MPEG:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "");
                		    break;
    
                    case GTYPE_MIME:
                    default:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											"");
                            break;
            	}

                PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
										" %s\n", name);
			    PR_Free(newpath);

                CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
			  }	
          }
      }

    return(CE_STATUS); /* keep going */

}

/* net_begin_gopher_cso
 *
 * adds formatting to the front of the document to make
 * it look pretty
 */
PRIVATE int
net_begin_gopher_cso(GopherConData * connection_data)
{
    PL_strcpy(CD_OUTPUT_BUF, "

CSO Search Results

\n
");

    CD_NEXT_STATE = GOPHER_TRANSFER_CSO;

    return(PUTSTRING(CD_OUTPUT_BUF));
}

/* 
 * parse cso output
 *
 * receives date from the cso server and turns it into HTML
 */
PRIVATE int 
net_parse_cso (ActiveEntry * cur_entry)
{
    char *line;
    char *colon1, *colon2; 
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;
    
    CE_STATUS = NET_BufferedReadLine(CE_SOCK, &line, &CD_DATA_BUF,
                                                &CD_DATA_BUF_SIZE, &CD_PAUSE_FOR_READ);

    if(CE_STATUS == 0)
    CE_STATUS = MK_SERVER_DISCONNECTED;
    CE_URL_S->error_msg = NET_ExplainErrorDetails(MK_SERVER_DISCONNECTED);
    
    /* if TCP error of if there is not a full line yet return
     */
    if(CE_STATUS < 0)
      {
        NET_ExplainErrorDetails(MK_TCP_READ_ERROR, PR_GetOSError());

        /* return TCP error
         */
        return MK_TCP_READ_ERROR;
      }
 	else if(!line)
      {
         return CE_STATUS;
      }

    if(CE_STATUS > 1)
      { 
        CE_BYTES_RECEIVED += CE_STATUS;
        FE_GraphProgress(CE_WINDOW_ID, CE_URL_S, CE_BYTES_RECEIVED, CE_STATUS, CE_URL_S->content_length);
      }

    /* a line beginning with a 2 means the end of data
     */
    if (*line == '2')
      {
    	CD_NEXT_STATE = GOPHER_DONE;
        CD_PAUSE_FOR_READ = FALSE;
        return(MK_DATA_LOADED);
      }
            
    /* if it starts with a 5 something went wrong, print
     * out the error message 
     */
    if (*line == '5') 
      {
    	PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "

%s

", line+4); PUTSTRING(CD_OUTPUT_BUF); CD_NEXT_STATE = GOPHER_DONE; CD_PAUSE_FOR_READ = FALSE; return(MK_DATA_LOADED); } if(*line == '-') { colon1 = PL_strchr(line,':'); if(colon1) colon2 = PL_strchr(colon1+1, ':'); else colon2 = NULL; if(colon2 != NULL) { if (*(colon2-1) != CD_CSO_LAST_CHAR) { /* print seperator */ PL_strcpy(CD_OUTPUT_BUF, "

"); CE_STATUS = PUTSTRING(CD_OUTPUT_BUF); } if(CE_STATUS > -1) CE_STATUS = PUTSTRING(colon2+1); PL_strcpy(CD_OUTPUT_BUF, "\n"); if(CE_STATUS > -1) CE_STATUS = PUTSTRING(CD_OUTPUT_BUF); if (*(colon2-1) != CD_CSO_LAST_CHAR) { /* end seperator */ PL_strcpy(CD_OUTPUT_BUF, "

");
				if(CE_STATUS > -1)
    				CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);
              }
                                    
            /* remember the last char so that we can
			 * tell when the sequence number changes
             */
            CD_CSO_LAST_CHAR =  *(colon2-1);
                
          } /* end if colon2 */
      } /* end if *line == '-' */
    
    return(1); /* keep going */

} /* end of procedure */

/*  display the page that allows searching of a gopher index 
 */
PRIVATE int 
net_display_index_splash_screen (ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;
	char *address_copy = 0;

	StrAllocCopy(address_copy, CE_URL_S->address);
	NET_UnEscape(address_copy);

    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, XP_GetString(XP_HTML_GOPHER_INDEX), address_copy, address_copy);
    PUTSTRING(CD_OUTPUT_BUF);

    COMPLETE_STREAM;

	PR_Free(address_copy);

    return(0);
}


/* parse CSO server output
*/
PRIVATE int 
net_display_cso_splash_screen (ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;
    char *address_copy = 0;

    StrAllocCopy(address_copy, CE_URL_S->address);
    NET_UnEscape(address_copy);

    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, XP_GetString(XP_HTML_CSO_SEARCH), address_copy, address_copy);

   	PUTSTRING(CD_OUTPUT_BUF);

    COMPLETE_STREAM;

	PR_Free(address_copy);

    return(0);
}

/* send the request to get the data
 */
PRIVATE int
net_send_gopher_request (ActiveEntry * cur_entry)
{

    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;

    TRACEMSG(("MKGopher: Connected, writing command `%s' to socket %d\n", 
                            CD_COMMAND, CE_SOCK));

    CE_STATUS = (int) NET_BlockingWrite(CE_SOCK, CD_COMMAND, PL_strlen(CD_COMMAND));

    NET_Progress (CE_WINDOW_ID, XP_GetString(XP_PROGRESS_WAITREPLY_GOTHER));

	/* start the graph progress indicator
     */
    FE_GraphProgressInit(CE_WINDOW_ID, CE_URL_S, CE_URL_S->content_length);
    CD_DESTROY_GRAPH_PROGRESS = TRUE;  /* we will need to destroy it */
    CD_ORIGINAL_CONTENT_LENGTH = CE_URL_S->content_length;

    CD_NEXT_STATE = GOPHER_BEGIN_TRANSFER;
    CD_PAUSE_FOR_READ = TRUE;

	if(CE_STATUS < 0)
		CE_STATUS = MK_TCP_WRITE_ERROR;
    
    return(CE_STATUS); /* everything OK */

} /* end GopherLoad */


/* pull down data in binary mode
 */
PRIVATE int
net_pull_gopher_data(ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;
    unsigned int write_ready, read_size;

    /* check to see if the stream is ready for writing
     */
    write_ready = (*CD_STREAM->is_write_ready)(CD_STREAM);

    if(write_ready < 1)
      {
        CD_PAUSE_FOR_READ = TRUE;
        return(0);  /* wait until we are ready to write */
      }
    else if(write_ready < (unsigned int) NET_Socket_Buffer_Size)
      {
        read_size = write_ready;
      }
    else
      {
        read_size = NET_Socket_Buffer_Size;
      }

    CE_STATUS = PR_Read(CE_SOCK, NET_Socket_Buffer, read_size);
 
    if(CE_STATUS == 0)
      {  /* all done */
    	CD_NEXT_STATE = GOPHER_DONE;
    	CE_STATUS = MK_DATA_LOADED;
      }
    else if(CE_STATUS > 0)
      {
	    CE_BYTES_RECEIVED += CE_STATUS;
        FE_GraphProgress(CE_WINDOW_ID, 
						 CE_URL_S, 
						 CE_BYTES_RECEIVED, 
						 CE_STATUS, 
						 CE_URL_S->content_length);
    	CE_STATUS = PUTBLOCK(NET_Socket_Buffer, CE_STATUS);
    	CD_PAUSE_FOR_READ = TRUE;
      }
	else
	  {
		/* status less than zero
		 */
		int rv = PR_GetError();
		if(rv == PR_WOULD_BLOCK_ERROR)
		  {
			CD_PAUSE_FOR_READ = TRUE;
			return 0;
		  }
		CE_STATUS = (rv < 0) ? rv : (-rv);
	  }
   
    return(CE_STATUS);

}

/* a list of dis-allowed ports for gopher
 * connections
 */
PRIVATE int bad_ports_table[] = { 
1, 7, 9, 11 , 13, 15, 17, 19, 20,
21, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102,
103, 104, 109, 110, 111, 113, 115, 117, 119,
135, 143, 512, 513, 514, 515, 526, 530, 531, 532, 
540, 556, 601, 6000, 0 };

/* begin the load by initializing data structures and calling Process Gopher
 */
PRIVATE int32
net_GopherLoad (ActiveEntry * cur_entry)
{
    char gopher_type;       /* type */
    char * path;            /* the URL path */
    char * gopher_path;        /* the gopher path */
	char * gopher_host;
    char * query;           /* holds the '?' query string */
    char *ptr;
    GopherConData * connection_data;    /* state data for this connection */

	/* check for illegal gopher port numbers and
	 * return invalid URL for those
	 */
	gopher_host = NET_ParseURL(CE_URL_S->address, GET_HOST_PART);
	if(gopher_host)
	  {
		int port_number;
		int i;
		char *colon = PL_strchr(gopher_host, ':');

		if(colon)
		  {
			colon++;  /* now it points to a port number */
			
			port_number = XP_ATOI(colon);

			/* disallow well known ports */
			for(i=0; bad_ports_table[i]; i++)
				if(port_number == bad_ports_table[i])
			  	  {
        			char *error_msg = NET_ExplainErrorDetails(
														MK_MALFORMED_URL_ERROR, 
														CE_URL_S->address);
					if(error_msg)
						FE_Alert(CE_WINDOW_ID, error_msg);
					PR_Free(gopher_host);
					return(MK_MALFORMED_URL_ERROR);
			  	  }
		  }
		PR_Free(gopher_host);
	  }
		

    /* malloc space for connection data 
     */
    connection_data = PR_NEW(GopherConData);
    if(!connection_data)
	  {
        CE_URL_S->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY);
        return(MK_OUT_OF_MEMORY);
	  }
    memset(connection_data, 0, sizeof(GopherConData));  /* zero it */

    CE_SOCK = NULL;

    cur_entry->con_data = connection_data;

	CD_OUTPUT_BUF = (char*) PR_Malloc(CD_OUTPUT_BUFFER_SIZE);
	if(!CD_OUTPUT_BUF)
	  {
        CE_URL_S->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY);
		return(MK_OUT_OF_MEMORY);
	  }
   
    /* Get entity type, and gopher_path string.
     */        
    path = NET_ParseURL(CE_URL_S->address, GET_PATH_PART);

    TRACEMSG(("GopherLoad: Got path: %s\n",path));

    if(*path && *path != '/') 
      {
        gopher_type = *path;   
        gopher_path = path+1; 
      }
    else if ((*path=='/') && (*(path+1)))   /* past first slash slash */
      {
    	gopher_type = *(path+1);  /* get gopher_type */
        gopher_path = path+2;  /* go past slash and gopher_type */
      }
    else /* no path or just slash */
      {
		/* special case!!!
		 * if the URL looks like gopher://host/?search term
		 * treat it as a text file and append the search term
		 */
		if(PL_strchr(CE_URL_S->address, '?'))
		  {
			gopher_type = '0';
		  }
		else
		  {
			gopher_type = '1';      /* menus are the default */
		  }
		if(*path == '\0')
            gopher_path = path;    
		else
            gopher_path = path+1; 
      }

    CD_GOPHER_TYPE = gopher_type; /* set for later use */

    TRACEMSG(("URL: %s\ngopher_type: %c\nGopher_Path: %s\n", 
                    CE_URL_S->address, gopher_type, gopher_path));

    /* We now have the gopher type so we can safely set up
     * the Stream stack since we know what type will be
     * returned ahead of time
     */
    switch(gopher_type) {
        case GTYPE_HTML:    /* all HTML types */
        case GTYPE_HTMLCAPS:
        case GTYPE_MENU:
        case GTYPE_CSO:
        case GTYPE_INDEX:
            StrAllocCopy(CE_URL_S->content_type, TEXT_HTML);
            break;
        case GTYPE_TEXT:
            StrAllocCopy(CE_URL_S->content_type, TEXT_PLAIN);
            break;
        case GTYPE_MACBINHEX:
            StrAllocCopy(CE_URL_S->content_type, APPLICATION_BINHEX);
            break;
        case GTYPE_PCBINARY:
        case GTYPE_BINARY:
        case GTYPE_MIME:
            StrAllocCopy(CE_URL_S->content_type, APPLICATION_OCTET_STREAM);
            break;
        case GTYPE_UUENCODED:
            StrAllocCopy(CE_URL_S->content_type, APPLICATION_UUENCODE);
            break;
        case GTYPE_TELNET:
        case GTYPE_TN3270:
#ifdef MOZILLA_CLIENT
            /* do the telnet and return */
            return(NET_RemoteHostLoad(cur_entry));
#else
			PR_ASSERT(0);
			break;
#endif /* MOZILLA_CLIENT */
        case GTYPE_GIF:
        case GTYPE_IMAGE:
            StrAllocCopy(CE_URL_S->content_type, IMAGE_GIF);
            break;
        case GTYPE_SOUND:
            StrAllocCopy(CE_URL_S->content_type, AUDIO_BASIC);
            break;
        case GTYPE_MPEG:
            StrAllocCopy(CE_URL_S->content_type, VIDEO_MPEG);
            break;
        default:
            StrAllocCopy(CE_URL_S->content_type, TEXT_PLAIN);
            break;
    }

    CD_STREAM = NET_StreamBuilder(cur_entry->format_out, CE_URL_S, CE_WINDOW_ID);

    if (!CD_STREAM) 
      {
        CE_URL_S->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONVERT);
        return(MK_UNABLE_TO_CONVERT);
      }

    TRACEMSG(("MKGopher: Stream now set up \n"));

    /* now do all the special handling of each type that
     * doesn't involve just a direct get 
     */
    switch(gopher_type) {
        case GTYPE_INDEX:
            /* Search is allowed */
            query = PL_strchr(CE_URL_S->address, '?');  /* Look for search string */

            if (!query || !query[1]) {      /* No search defined */
                /* Display "cover page" */
                net_display_index_splash_screen(cur_entry);
                CD_NEXT_STATE = GOPHER_DONE;
                return -1;      /* Local function only */
            }

            *query++ = 0;     /* Go past '?' */
          
            StrAllocCopy(CD_COMMAND, NET_UnEscape(gopher_path));

            StrAllocCat(CD_COMMAND, "\t");
      
            /* Remove plus signs */
            for (ptr=query; *ptr; ptr++) 
                if (*ptr == '+') 
				    *ptr = ' ';

            StrAllocCat(CD_COMMAND, NET_UnEscape(query));

			*(query-1) = '?';  /* set it back to the way it was */
            break;

        case GTYPE_CSO:
            /* Search is allowed */
            query = PL_strchr(CE_URL_S->address, '?');      /* Look for search string */

            if (!query || !query[1])           /* No search required */
			  {
                /* Display "cover page" */
                net_display_cso_splash_screen(cur_entry);     
                CE_STATUS = MK_DATA_LOADED;
                return -1;      /* Local function only */
              }
            *query++ = 0;       /* Go past '?' */

            StrAllocCopy(CD_COMMAND, "query ");

            /* Remove plus signs */
            for (ptr=query; *ptr; ptr++) 
                if (*ptr == '+') 
				    *ptr = ' ';

            StrAllocCat(CD_COMMAND, (char *)NET_UnEscape(query));

			*(query-1) = '?';  /* set it back to the way it was */
           
			break;

	    case GTYPE_TEXT:
		   /* Look for search string */
		    query = PL_strchr(CE_URL_S->address, '?');     

			/* special case!!!
			 * if a query exist treat it special, send 
			 * the query instead
			 */
			if(query)
			  {
				*query++ = 0;       /* Go past '?' */

				/* Remove plus signs */
				for (ptr=query; *ptr; ptr++) 
				  if (*ptr == '+') 
				    *ptr = ' ';

				StrAllocCopy(CD_COMMAND, (char *)NET_UnEscape(query));
				
				*(query-1) = '?';  /* set it back to the way it was */
			  }	
			else if(*path != '\0')
			  {
				StrAllocCopy(CD_COMMAND, (char*)NET_UnEscape(gopher_path));
			  }
			else
			  {
				StrAllocCopy(CD_COMMAND, "/");
			  }
			break;

        default:   /* all other types besides index and CSO */
            if(*path == '\0')
                StrAllocCopy(CD_COMMAND, "/");
            else
                StrAllocCopy(CD_COMMAND, (char*)NET_UnEscape(gopher_path));
    }

    PR_FREEIF(path);  /* NET_Parse malloc'd the path string */
    path = NULL;

    /* protect against other protocol attacks by limiting
     * the command to a single line.  Terminate the command
     * at any \n or \r
     */
    if(PL_strchr(CD_COMMAND, '\n') || PL_strchr(CD_COMMAND, '\r'))
      {
   		char *error_msg = NET_ExplainErrorDetails(MK_MALFORMED_URL_ERROR, 
												  CE_URL_S->address);
		if(error_msg)
			FE_Alert(CE_WINDOW_ID, error_msg);
        return(MK_MALFORMED_URL_ERROR);
      }
    
    StrAllocCat(CD_COMMAND, CRLF); /* finish off the command */

    CD_NEXT_STATE = GOPHER_BEGIN_CONNECT;

    return(net_ProcessGopher(cur_entry));
}

/* NET_ProcessGopher
 *    completes the data transfer; is called from NET_ProcessNet()
 *
 * returns negative when complete
 */
PRIVATE int32
net_ProcessGopher(ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;

    TRACEMSG(("Entered ProcessGopher: gopher_type=%c\n",CD_GOPHER_TYPE));

    CD_PAUSE_FOR_READ = FALSE;

    while(!CD_PAUSE_FOR_READ)
      {
        TRACEMSG(("ProcessGopher: in switch with state: #%d\n",CD_NEXT_STATE));

        switch(CD_NEXT_STATE) {

    	case GOPHER_BEGIN_CONNECT:
            /*  Set up a socket to the server for the data:
            */
            TRACEMSG(("MKGopher: Setting up net connection\n"));

            CE_STATUS = NET_BeginConnect(CE_URL_S->address, 
										 CE_URL_S->IPAddressString,
										 "Gopher", 
										 70, 
										 &CE_SOCK, 
										 HG10300 
										 &CD_TCP_CON_DATA, 
										 CE_WINDOW_ID,
										 &CE_URL_S->error_msg,
										 cur_entry->socks_host,
										 cur_entry->socks_port,
                                         cur_entry->URL_s->localIP);

			if(CE_SOCK != NULL)
				NET_TotalNumberOfOpenConnections++;

        	if(CE_STATUS == MK_CONNECTED)
          	  {
        		CD_NEXT_STATE = GOPHER_SEND_REQUEST;
		    	NET_SetReadSelect(CE_WINDOW_ID, CE_SOCK);
              }
            else if(CE_STATUS > -1)
              {
        	    CD_NEXT_STATE = GOPHER_FINISH_CONNECT;
        	    CD_PAUSE_FOR_READ = TRUE;
        	    CE_CON_SOCK = CE_SOCK; /* set so we select on it */
                NET_SetConnectSelect(CE_WINDOW_ID, CE_CON_SOCK);
              }
            break;

        case GOPHER_FINISH_CONNECT:
            CE_STATUS = NET_FinishConnect(CE_URL_S->address,
										  "Gopher",
										  70, 
										  &CE_SOCK, 
										  &CD_TCP_CON_DATA, 
										  CE_WINDOW_ID,
										  &CE_URL_S->error_msg,
                                          cur_entry->URL_s->localIP);
            if(CE_STATUS == MK_CONNECTED)
              {
        	    CD_NEXT_STATE = GOPHER_SEND_REQUEST;
				NET_ClearConnectSelect(CE_WINDOW_ID, CE_CON_SOCK);
        	    CE_CON_SOCK = NULL;  /* reset so we don't select on it */
				NET_SetReadSelect(CE_WINDOW_ID, CE_SOCK);
              }
            else
              {

                /* unregister the old CE_SOCK from the select list
                 * and register the new value in the case that it changes
                 */
                if(CE_CON_SOCK != CE_SOCK)
                  {
                    NET_ClearConnectSelect(CE_WINDOW_ID, CE_CON_SOCK);
                    CE_CON_SOCK = CE_SOCK;
                    NET_SetConnectSelect(CE_WINDOW_ID, CE_CON_SOCK);
                  }

        	    CD_PAUSE_FOR_READ = TRUE;
              }
            break;

            case GOPHER_SEND_REQUEST:
            CE_STATUS = net_send_gopher_request(cur_entry);
            break;
       
        case GOPHER_BEGIN_TRANSFER:
            if(CD_GOPHER_TYPE == GTYPE_MENU || CD_GOPHER_TYPE == GTYPE_INDEX)
                CE_STATUS = net_begin_gopher_menu(connection_data);
            else if(CD_GOPHER_TYPE == GTYPE_CSO)
                CE_STATUS = net_begin_gopher_cso(connection_data);
            else
                CD_NEXT_STATE = GOPHER_TRANSFER_BINARY;
            break;

        case GOPHER_TRANSFER_MENU:
            CE_STATUS = net_parse_menu(cur_entry);
            break;

        case GOPHER_TRANSFER_CSO:
            CE_STATUS = net_parse_cso(cur_entry);
            break;
    
        case GOPHER_TRANSFER_BINARY:
            CE_STATUS = net_pull_gopher_data(cur_entry);
            break;
    
        case GOPHER_DONE:
			NET_ClearReadSelect(CE_WINDOW_ID, CE_SOCK);
            PR_Close(CE_SOCK);
			NET_TotalNumberOfOpenConnections--;
            COMPLETE_STREAM;
            CD_NEXT_STATE = GOPHER_FREE;
            break;

        case GOPHER_ERROR_DONE:
            if(CE_SOCK != NULL)
			  {
				NET_ClearReadSelect(CE_WINDOW_ID, CE_SOCK);
				NET_ClearConnectSelect(CE_WINDOW_ID, CE_SOCK);
                NET_ClearDNSSelect(CE_WINDOW_ID, CE_SOCK);
                PR_Close(CE_SOCK);
				NET_TotalNumberOfOpenConnections--;
			  }
            if(CD_STREAM)
                ABORT_STREAM(CE_STATUS);
            CD_NEXT_STATE = GOPHER_FREE;
            break;
    
        case GOPHER_FREE:
            if(CD_DESTROY_GRAPH_PROGRESS)
                FE_GraphProgressDestroy(CE_WINDOW_ID,
                                        CE_URL_S,
                                        CD_ORIGINAL_CONTENT_LENGTH,
										CE_BYTES_RECEIVED);
            PR_FREEIF(CD_COMMAND);
            PR_FREEIF(CD_STREAM);  	/* don't forget the stream */
			PR_FREEIF(CD_OUTPUT_BUF);
            PR_FREEIF(CD_DATA_BUF);
            if(CD_TCP_CON_DATA)
                NET_FreeTCPConData(CD_TCP_CON_DATA);

            PR_Free(connection_data);
            return(-1);  /* all done */

    } /* end switch(NEXT_STATE) */


    if(CE_STATUS < 0 && CD_NEXT_STATE != GOPHER_DONE && 
                CD_NEXT_STATE != GOPHER_ERROR_DONE && 
                CD_NEXT_STATE != GOPHER_FREE)
      {
        CD_NEXT_STATE = GOPHER_ERROR_DONE;
        CD_PAUSE_FOR_READ = FALSE; 
      }
    
    } /* end while */

    return(CE_STATUS);
}

/* abort the connection in progress
 */
PRIVATE int32
net_InterruptGopher(ActiveEntry * cur_entry)
{
    GopherConData * connection_data = (GopherConData *)cur_entry->con_data;

    CD_NEXT_STATE = GOPHER_ERROR_DONE;
    CE_STATUS = MK_INTERRUPTED;
 
    return(net_ProcessGopher(cur_entry));
}

/* Free any memory 
 */
PRIVATE void
net_CleanupGopher(void)
{
    /* nothing to free */
    return;
}

MODULE_PRIVATE void
NET_InitGopherProtocol(void)
{
    static NET_ProtoImpl gopher_proto_impl;

    gopher_proto_impl.init = net_GopherLoad;
    gopher_proto_impl.process = net_ProcessGopher;
    gopher_proto_impl.interrupt = net_InterruptGopher;
    gopher_proto_impl.cleanup = net_CleanupGopher;

    NET_RegisterProtocolImplementation(&gopher_proto_impl, GOPHER_TYPE_URL);
}