/* -*- 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 "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, "<H1>Gopher Menu</H1>\n<PRE>");
   	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;
	char *gopher_path=0; 
    char *port;         
    char *ptr;
    char *line;
    char *host;        

    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, "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-text\">");
        CE_STATUS = PUTSTRING(CD_OUTPUT_BUF);

		if(CE_STATUS < 0)
			return(CE_STATUS);

    	PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "<A HREF=\"%s\">%s</A>\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, "<A HREF=\"telnet://%s@%s/\"><IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-telnet\"> %s</A>\n", temp, host, name);
			PR_Free(temp);
		  }
        else 
		  {
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "<A HREF=\"telnet://%s/\"><IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-telnet\"> %s</A>\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, "<A HREF=\"tn3270://%s@%s/\"><IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-telnet\"> %s</A>\n", gopher_path, host, name);
        else 
		    PR_snprintf(CD_OUTPUT_BUF, CD_OUTPUT_BUFFER_SIZE, "<A HREF=\"tn3270://%s/\"><IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-telnet\"> %s</A>\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, "<A HREF=\"gopher://%s/%c%s\">", 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),
									    "<IMG ALIGN=absbottom BORDER=0 "
										"SRC=\"internal-gopher-text\">");
                            break;
                    case GTYPE_MENU:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-menu\">");
                            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),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-index\">");
                            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),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-binary\">");
                            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),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-image\">");
                            break;
                    case GTYPE_SOUND:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-sound\">");
                            break;
        		    case GTYPE_MPEG:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											    "<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-movie\">");
                		    break;
    
                    case GTYPE_MIME:
                    default:
            			    PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
											"<IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-unknown\">");
                            break;
            	}

                PR_snprintf(&CD_OUTPUT_BUF[PL_strlen(CD_OUTPUT_BUF)], 
										CD_OUTPUT_BUFFER_SIZE 
												- PL_strlen(CD_OUTPUT_BUF),
										" %s</A>\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, "<H1>CSO Search Results</H1>\n<PRE>");

    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, "<H2>%s</H2>", 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, "</PRE><H2>");
    			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, "</H2><PRE>");
				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, 
										 FALSE, 
										 &CD_TCP_CON_DATA, 
										 CE_WINDOW_ID,
										 &CE_URL_S->error_msg,
										 cur_entry->socks_host,
										 cur_entry->socks_port);

			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);
            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);
}
