/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
 *                             / __| | | | |_) | |    
 *                            | (__| |_| |  _ <| |___ 
 *                             \___|\___/|_| \_\_____|
 *
 *  The contents of this file are subject to the Mozilla Public License
 *  Version 1.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *  http://www.mozilla.org/MPL/
 *
 *  Software distributed under the License is distributed on an "AS IS"
 *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *  License for the specific language governing rights and limitations
 *  under the License.
 *
 *  The Original Code is Curl.
 *
 *  The Initial Developer of the Original Code is Daniel Stenberg.
 *
 *  Portions created by the Initial Developer are Copyright (C) 1998.
 *  All Rights Reserved.
 *
 *  Contributor(s):
 *   Rafael Sagula <sagula@inf.ufrgs.br>
 *   Sampo Kellomaki <sampo@iki.fi>
 *   Linas Vepstas <linas@linas.org>
 *   Bjorn Reese <breese@imada.ou.dk>
 *   Johan Anderson <johan@homemail.com>
 *   Kjell Ericson <Kjell.Ericson@sth.frontec.se>
 *   Troy Engel <tengel@palladium.net>
 *   Ryan Nelson <ryan@inch.com>
 *   Bjorn Stenberg <Bjorn.Stenberg@sth.frontec.se>
 *   Angus Mackay <amackay@gus.ml.org>
 *
 * ------------------------------------------------------------
 * Main author:
 * - Daniel Stenberg <Daniel.Stenberg@sth.frontec.se>
 *
 * 	http://www.fts.frontec.se/~dast/curl/
 *
 * $Source: /cvs/curl/curl/lib/url.c,v $
 * $Revision: 1.10 $
 * $Date: 1999/04/19 22:22:00 $
 * $Author: dast $
 * $State: Exp $
 * $Locker:  $
 *
 * ------------------------------------------------------------
 ****************************************************************************/

/*
 * SSL code intially written by
 * Linas Vepstas <linas@linas.org> and Sampo Kellomaki <sampo@iki.fi>
 */

/* -- WIN32 approved -- */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>

#include "../include/setup.h"

#if defined(WIN32) && !defined(__GNUC__)
#include <winsock.h>
#include <time.h>
#include <io.h>
#else
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/resource.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <signal.h>

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifndef HAVE_VPRINTF
#error "We can't compile without vprintf() support!"
#endif
#ifndef HAVE_SELECT
#error "We can't compile without select() support!"
#endif
#ifndef HAVE_SOCKET
#error "We can't compile without socket() support!"
#endif

#endif

#include "urldata.h"
#include "../include/curl.h"
#include "netrc.h"

#include "formdata.h"
#include "getenv.h"
#include "base64.h"
#include "ssluse.h"
#include "hostip.h"
#include "if2ip.h"
#include "upload.h"
#include "download.h"
#include "sendf.h"
#include "speedcheck.h"
#include "getpass.h"
#include "ftp.h"
#include "progress.h"
#include "cookie.h"

#define _MPRINTF_REPLACE /* use our functions only */
#include "../include/mprintf.h"

/* -- -- */

/***********************************************************************
 * Start with some silly functions to make win32-systems survive
 ***********************************************************************/
#if defined(WIN32) && !defined(__GNUC__)
static void cleanup(void)
{
  WSACleanup();
}

static int init(void)
{
  WORD wVersionRequested;  
  WSADATA wsaData; 
  int err; 
  wVersionRequested = MAKEWORD(1, 1); 
    
  err = WSAStartup(wVersionRequested, &wsaData); 
    
  if (err != 0) 
    /* Tell the user that we couldn't find a useable */ 
    /* winsock.dll.     */ 
    return 1; 
    
  /* Confirm that the Windows Sockets DLL supports 1.1.*/ 
  /* Note that if the DLL supports versions greater */ 
  /* than 1.1 in addition to 1.1, it will still return */ 
  /* 1.1 in wVersion since that is the version we */ 
  /* requested. */ 
    
  if ( LOBYTE( wsaData.wVersion ) != 1 || 
       HIBYTE( wsaData.wVersion ) != 1 ) { 
    /* Tell the user that we couldn't find a useable */ 

    /* winsock.dll. */ 
    WSACleanup(); 
    return 1; 
  }
  return 0;
}
/* The Windows Sockets DLL is acceptable. Proceed. */ 
#else
static int init(void) { return 0; }
static void cleanup(void) {}
#endif

static UrgError _urlget(struct UrlData *data);


void urlfree(struct UrlData *data, bool totally)
{
  /* close possibly still open sockets */
  if(-1 != data->secondarysocket) {
    sclose(data->secondarysocket);
    data->secondarysocket = -1;	
  }
  if(-1 != data->firstsocket) {
    sclose(data->firstsocket);
    data->firstsocket=-1;
  }

#ifdef USE_SSLEAY
  if (data->use_ssl) {
    if(data->ssl)
      SSL_free (data->ssl);
    if(data->ctx)
      SSL_CTX_free (data->ctx);
  }
#endif /* USE_SSLEAY */

  if(data->ptr_proxyuserpwd) {
    free(data->ptr_proxyuserpwd);
    data->ptr_proxyuserpwd=NULL;
  }
  if(data->ptr_uagent) {
    free(data->ptr_uagent);
    data->ptr_uagent=NULL;
  }
  if(data->ptr_userpwd) {
    free(data->ptr_userpwd);
    data->ptr_userpwd=NULL;
  }
  if(data->ptr_rangeline) {
    free(data->ptr_rangeline);
    data->ptr_rangeline=NULL;
  }
  if(data->ptr_ref) {
    free(data->ptr_ref);
    data->ptr_ref=NULL;
  }
  if(data->ptr_cookie) {
    free(data->ptr_cookie);
    data->ptr_cookie=NULL;
  }

  if(totally) {
    /* we let the switch decide whether we're doing a part or total
       cleanup */

    /* check for allocated [URL] memory to free: */
    if(data->freethis)
      free(data->freethis);

    if(data->headerbuff)
      free(data->headerbuff);

    cookie_cleanup(data->cookies);

    free(data);

    /* winsock crap cleanup */
    cleanup();
  }
}

typedef int (*func_T)(void);

UrgError curl_urlget(UrgTag tag, ...)
{
  va_list arg;
  func_T param_func = (func_T)0;
  long param_long = 0;
  void *param_obj = NULL;
  UrgError res;
  char *cookiefile;

  struct UrlData *data;

  /* this is for the lame win32 socket crap */
  if(init())
    return URG_FAILED_INIT;

  data = (struct UrlData *)malloc(sizeof(struct UrlData));
  if(data) {

    memset(data, 0, sizeof(struct UrlData));

    /* Let's set some default values: */
    data->out = stdout; /* default output to stdout */
    data->in  = stdin;  /* default input from stdin */
    data->firstsocket = -1; /* no file descriptor */
    data->secondarysocket = -1; /* no file descriptor */

    /* use fwrite as default function to store output */
    data->fwrite = (size_t (*)(char *, size_t, size_t, FILE *))fwrite;

    /* use fread as default function to read input */
    data->fread = (size_t (*)(char *, size_t, size_t, FILE *))fread;

    data->infilesize = -1; /* we don't know any size */

    data->current_speed = -1; /* init to negative == impossible */

    va_start(arg, tag);

    while(tag != URGTAG_DONE) {
      /* PORTING NOTE:
	 Ojbect pointers can't necessarily be casted to function pointers and
	 therefore we need to know what type it is and read the correct type
	 at once. This should also correct problems with different sizes of
	 the types.
         */

      if(tag < URGTYPE_OBJECTPOINT) {
	/* This is a LONG type */
	param_long = va_arg(arg, long);
      }
      else if(tag < URGTYPE_FUNCTIONPOINT) {
	/* This is a object pointer type */
	param_obj = va_arg(arg, void *);
      }
      else
	param_func = va_arg(arg, func_T );

      /* printf("tag: %d\n", tag); */
     

      switch(tag) {
#ifdef MULTIDOC
      case URGTAG_MOREDOCS:
        data->moredoc = (struct MoreDoc *)param_obj;
        break;
#endif
      case URGTAG_SSLVERSION:
        data->ssl_version = (int)param_obj;
        break;

      case URGTAG_COOKIEFILE:
        cookiefile = (char *)param_obj;
        if(cookiefile) {
          data->cookies = cookie_init(cookiefile);
        }
        break;
      case URGTAG_WRITEHEADER:
	data->writeheader = (FILE *)param_obj;
	break;
      case URGTAG_COOKIE:
	data->cookie = (char *)param_obj;
	break;
      case URGTAG_ERRORBUFFER:
        data->errorbuffer = (char *)param_obj;
        break;
      case URGTAG_FILE:
        data->out = (FILE *)param_obj;
        break;
      case URGTAG_FTPPORT:
        data->ftpport = (char *)param_obj;
        break;
      case URGTAG_HTTPHEADER:
	data->headers = (struct HttpHeader *)param_obj;
	break;
      case URGTAG_HTTPPOST:
	data->httppost = (struct HttpPost *)param_obj;
	break;
      case URGTAG_INFILE:
        data->in = (FILE *)param_obj;
        break;
      case URGTAG_INFILESIZE:
        data->infilesize = (long)param_long;
        break;
      case URGTAG_LOW_SPEED_LIMIT:
	data->low_speed_limit=(long)param_long;
	break;
      case URGTAG_LOW_SPEED_TIME:
	data->low_speed_time=(long)param_long;
	break;
      case URGTAG_URL:
        data->url = (char *)param_obj;
        break;
      case URGTAG_PORT:
        /* this typecast is used to fool the compiler to NOT warn for a
           "cast from pointer to integer of different size" */
        data->port = (unsigned short)((long)param_long);
        break;
      case URGTAG_POSTFIELDS:
        data->postfields = (char *)param_obj;
        break;
      case URGTAG_REFERER:
        data->referer = (char *)param_obj;
        break;
      case URGTAG_PROXY:
        data->proxy = (char *)param_obj;
        break;
      case URGTAG_FLAGS:
        data->conf = (long)param_long;
        break;
      case URGTAG_TIMEOUT:
        data->timeout = (long)param_long;
        break;
      case URGTAG_USERAGENT:
        data->useragent = (char *)param_obj;
        break;
      case URGTAG_USERPWD:
        data->userpwd = (char *)param_obj;
        break;
      case URGTAG_PROXYUSERPWD:
        data->proxyuserpwd = (char *)param_obj;
        break;
      case URGTAG_RANGE:
        data->range = (char *)param_obj;
        break;
      case URGTAG_RESUME_FROM:
	data->resume_from = (long)param_long;
	break;
      case URGTAG_WRITEFUNCTION:
        data->fwrite = (size_t (*)(char *, size_t, size_t, FILE *))param_func;
        break;
      case URGTAG_READFUNCTION:
        data->fread = (size_t (*)(char *, size_t, size_t, FILE *))param_func;
        break;
      case URGTAG_SSLCERT:
	data->cert = (char *)param_obj;
	break;
      case URGTAG_SSLCERTPASSWD:
	data->cert_passwd = (char *)param_obj;
	break;
      case URGTAG_CRLF:
	data->crlf = (long)param_long;
	break;
      case URGTAG_QUOTE:
        data->quote = (struct slist *)param_obj;
        break;
      case URGTAG_DONE: /* done with the parsing, fall through */
        continue;
      default:
        /* unknown tag and its companion, just ignore: */
        break;
      }
      tag = va_arg(arg, UrgTag);
    }

    va_end(arg);

    data-> headerbuff=(char*)malloc(HEADERSIZE);
    if(!data->headerbuff)
      return URG_FAILED_INIT;

    data-> headersize=HEADERSIZE;

    res = _urlget(data); /* fetch the URL please */

    while((res == URG_OK) && data->newurl) {
      /* Location: redirect */
      char *freethis = data->newurl;

      char prot[16];
      char path[URL_MAX_LENGTH];

      if(2 != sscanf(data->newurl, "%15[^:]://%" URL_MAX_LENGTH_TXT
                     "s", prot, path)) {
	/***
	 *DANG* this is an RFC 2068 violation. The URL is supposed
	 to be absolute and this doesn't seem to be that!
	 At least the Zeus HTTP server seem to do this.
	 ***
	 Instead, we have to TRY to append this new path to the old URL
	 to the right of the host part. Oh crap, this is doomed to cause
	 problems in the future...
	 */
	char *protsep;
	char *pathsep;
	int length;
	char *newest;

	/* protsep points to the start of the host name */
	protsep=strstr(data->url, "//");
	if(!protsep)
	  protsep=data->url;
	else
	  protsep+=2; /* pass the // */

        if('/' != data->newurl[0]) {
          /* we have a relative path to append to the last slash if
             there's one available */
          pathsep = strrchr(protsep, '/');
          if(pathsep)
            *pathsep=0;
        }
        else {
          /* We got a new absolute path for this server, cut off from the
             first slash */
          pathsep = strchr(protsep, '/');
          if(pathsep)
            *pathsep=0;
        }

	newest = maprintf("%s%s%s", data->url, ('/' == data->newurl[0])?"":"/",
			  data->newurl);
	if(!newest)
	  return URG_OUT_OF_MEMORY;
	free(data->newurl);
	data->newurl = newest;
      }

      data->url = data->newurl;
      data->newurl = NULL; /* don't show! */

      infof(data, "Follows Location: to new URL: '%s'\n", data->url);

      /* clean up the sockets and SSL stuff from the previous "round" */
      urlfree(data, FALSE);

      res = _urlget(data);
      free(freethis);
    }

  }
  else
    res = URG_FAILED_INIT; /* failed */

  /* total cleanup */
  urlfree(data, TRUE);

  return res;
}


/*
 * Read everything until a newline.
 */

static int GetLine(int sockfd, char *buf,
		   struct UrlData *data)
{
  int nread;
  int read_rc=1;
  char *ptr;
  ptr=buf;

  /* get us a full line, terminated with a newline */
  for(nread=0;
      (nread<BUFSIZE) && read_rc;
      nread++, ptr++) {
#ifdef USE_SSLEAY
    if (data->use_ssl) {
      read_rc = SSL_read(data->ssl, ptr, 1);
    }
    else {
#endif
      read_rc = sread(sockfd, ptr, 1);
#ifdef USE_SSLEAY
    }
#endif /* USE_SSLEAY */
    if (*ptr == '\n')
      break;
  }
  *ptr=0; /* zero terminate */

  if(data->conf & CONF_VERBOSE) {
    fputs("< ", stderr);
    fwrite(buf, 1, nread, stderr);
    fputs("\n", stderr);
  }
  return nread;
}



#ifndef WIN32
#ifndef RETSIGTYPE
#define RETSIGTYPE void
#endif
RETSIGTYPE alarmfunc(int signal)
{
  /* this is for "-ansi -Wall -pedantic" to stop complaining!   (rabe) */
  (void)signal;
  return;
}
#endif

#ifdef TRY_TELNET
/* for telnet testing only */

char *telnetin(struct UrlData *data,
	       unsigned char inbyte)
{
#define TELNET_SIZE 256
  static char inbuffer[TELNET_SIZE+1];
  static int index=0;

  if(10 == inbyte) {
    /* newline, we reset buffer */
    index=0;
    inbuffer[0]=0;
#if 0
    fprintf(stderr, "[NOLL]\n");
#endif
  }
  else if(' ' > inbyte) {
    /* ignore low ASCII numbers */
#if 0
    fprintf(stderr, "[IGNORE] %u\n", inbyte);
#endif
  }
  else if(index < TELNET_SIZE) {
#if 0
    fprintf(stderr, "In:%c\n", inbyte);
#endif
    inbuffer[index]=inbyte;
    inbuffer[index+1]=0; /* zero terminate all times to allow string
			    functions to operate on buffer */
    index++;
  }
  /* else
     buffer is full, skip it */
  return inbuffer;
}

#endif

/* ====================================================== */
/*
 * urlget <url>
 * (result put on stdout)
 *
 * <url> ::= <proto> "://" <host> [ ":" <port> ] "/" <path>
 *
 * <proto> = "HTTP" | "HTTPS" | "GOPHER" | "FTP"
 *
 * When FTP:
 *
 * <host> ::= [ <user> ":" <password> "@" ] <host>
 */

static UrgError _urlget(struct UrlData *data)
{
  struct hostent *hp=NULL;
  struct sockaddr_in serv_addr;
  char *buf;
  char proto[64];
  char gname[256]="default.com";
  char *name;
  char path[URL_MAX_LENGTH]="/";
  char *ppath, *tmp;
  long bytecount;
  struct timeval now;
  char ftpuser[128]="";
  char ftppasswd[128]="";
  char proxyuser[128]="";
  char proxypasswd[128]="";

  UrgError result;
  char resumerange[12]="";

  buf = data->buffer; /* this is our buffer */

#ifndef WIN32
  signal(SIGALRM, alarmfunc);
#endif

  /* Parse <url> */
  /* We need to parse the url, even when using the proxy, because
   * we will need the hostname and port in case we are trying
   * to SSL connect through the proxy -- and we don't know if we
   * will need to use SSL until we parse the url ...
   */
  if (2 > sscanf(data->url, "%64[^\n:]://%256[^\n/]%" URL_MAX_LENGTH_TXT "[^\n]",
		   proto, gname, path)) {
      
    /* badly formatted, let's try the browser-style _without_ 'http://' */
    if((1 > sscanf(data->url, "%256[^\n/]%" URL_MAX_LENGTH_TXT "[^\n]", gname,
                   path)) ) {
      failf(data, "<url> malformed");
      return URG_URL_MALFORMAT;
    }
    if(strnequal(gname, "FTP", 3)) {
      strcpy(proto, "ftp");
    }
    else if(strnequal(gname, "GOPHER", 6))
      strcpy(proto, "gopher");
#ifdef USE_SSLEAY
    else if(strnequal(gname, "HTTPS", 5))
      strcpy(proto, "https");
#endif /* USE_SSLEAY */
#ifdef TRY_TELNET
    else if(strnequal(gname, "TELNET", 6))
      strcpy(proto, "telnet");
#endif
    else if (strnequal(gname, "DICT", sizeof("DICT")-1))
      strcpy(proto, "DICT");
    else
      strcpy(proto, "http");

    data->conf |= CONF_NOPROT;
  }


  if(data->conf & CONF_USERPWD) {
    if(':' != *data->userpwd) {
      if((1 <= sscanf(data->userpwd, "%127[^:]:%127s",
                      ftpuser, ftppasswd))) {
        /* check for password, if no ask for one */
        if( !ftppasswd[0] )
        {
          strncpy(ftppasswd, getpass("password: "), sizeof(ftppasswd));
        }
      }
    }
    if(!ftpuser[0]) {
      failf(data, "USER malformat: user name can't be zero length");
      return URG_MALFORMAT_USER;
    }
  }

  if(data->conf & CONF_PROXYUSERPWD) {
    if(':' != *data->proxyuserpwd) {
      if((1 <= sscanf(data->proxyuserpwd, "%127[^:]:%127s",
                      proxyuser, proxypasswd))) {
        /* check for password, if no ask for one */
        if( !proxypasswd[0] )
        {
          strncpy(proxypasswd, getpass("proxy password: "), sizeof(proxypasswd));
        }
      }
    }
    if(!proxyuser[0]) {
      failf(data, " Proxy USER malformat: user name can't be zero length");
      return URG_MALFORMAT_USER;
    }
  }

  name = gname;
  ppath = path;
  data->hostname = name;


  if(!(data->conf & CONF_PROXY)) {
    /* If proxy was not specified, we check for default proxy environment
       variables, to enable i.e Lynx compliance:

       HTTP_PROXY http://some.server.dom:port/
       HTTPS_PROXY http://some.server.dom:port/
       FTP_PROXY http://some.server.dom:port/
       GOPHER_PROXY http://some.server.dom:port/
       NO_PROXY host.domain.dom  (a comma-separated list of hosts which should
       not be proxied, or an asterisk to override all proxy variables)
       ALL_PROXY seems to exist for the CERN www lib. Probably the first to
       check for.
 
       */
    char *no_proxy=GetEnv("NO_PROXY");
    char *proxy=NULL;
    char proxy_env[128];

    if(!no_proxy || !strequal("*", no_proxy)) {
      /* NO_PROXY wasn't specified or it wasn't just an asterisk */
      char *nope;

      nope=no_proxy?strtok(no_proxy, ", "):NULL;
      while(nope) {
	if(strequal(nope, name)) {
	  /* no proxy for this host! */
	  break;
	}
	nope=strtok(NULL, ", ");
      }
      if(!nope) {
	/* It was not listed as without proxy */
	char *protop = proto;
	char *envp = proxy_env;
	char *prox;

	/* Now, build <PROTOCOL>_PROXY and check for such a one to use */
	while(*protop) {
	  *envp++ = toupper(*protop++);
	}
	/* append _PROXY */
	strcpy(envp, "_PROXY");
#if 0
	infof(data, "DEBUG: checks the environment variable %s\n", proxy_env);
#endif
	/* read the protocol proxy: */
	prox=GetEnv(proxy_env);

	if(prox && *prox) { /* don't count "" strings */
	  proxy = prox; /* use this */
        }
        else
          proxy = GetEnv("ALL_PROXY"); /* default proxy to use */

        if(proxy && *proxy) {
          /* we have a proxy here to set */
          data->proxy = proxy;
          data->conf |= CONF_PROXY;
        }
      } /* if (!nope) - it wasn't specfied non-proxy */
    } /* NO_PROXY wasn't specified or '*' */
  } /* if not using proxy */

  if((data->conf & (CONF_PROXY|CONF_NOPROT)) == (CONF_PROXY|CONF_NOPROT) ) {
    /* We're guessing prefixes here and since we're told to use a proxy, we
       need to add the protocol prefix to the URL string before we continue!
       */
    char *reurl;

    reurl = maprintf("%s://%s", proto, data->url);

    if(!reurl)
      return URG_OUT_OF_MEMORY;

    data->url = reurl;
    if(data->freethis)
      free(data->freethis);
    data->freethis = reurl;
  }

  /* RESUME on a HTTP page is a tricky business. First, let's just check that
     'range' isn't used, then set the range parameter and leave the resume as
     it is to inform about this situation for later use. We will then
     "attempt" to resume, and if we're talking to a HTTP/1.1 (or later)
     server, we will get the document resumed. If we talk to a HTTP/1.0
     server, we just fail since we can't rewind the file writing from within
     this function. */
  if(data->resume_from) {
    if(!(data->conf & CONF_RANGE)) {
      /* if it already was in use, we just skip this */
      sprintf(resumerange, "%d-", data->resume_from);
      data->range=resumerange; /* tell ourselves to fetch this range */
      data->conf |= CONF_RANGE; /* switch on range usage */
    }
  }


  if(data->timeout) {
    /* We set the timeout on the connection/resolving phase first, separately
       from the download/upload part to allow a maximum time on everything */
    myalarm(data->timeout); /* this sends a signal when the timeout fires
			       off, and that will abort system calls */
  }

  /*
   * Hmm, if we are using a proxy, then we can skip the GOPHER and the
   * FTP steps, although we cannot skip the HTTPS step (since the proxy
   * works differently, depending on whether its SSL or not).
   */

  if (strequal(proto, "HTTP")) {
    if(!data->port)
      data->port = PORT_HTTP;
    data->remote_port = PORT_HTTP;
    data->conf |= CONF_HTTP;
  }
  else if (strequal(proto, "HTTPS")) {
#ifdef USE_SSLEAY
    if(!data->port)
      data->port = PORT_HTTPS;
    data->remote_port = PORT_HTTPS;
    data->conf |= CONF_HTTP;
    data->conf |= CONF_HTTPS;
#else /* USE_SSLEAY */
    failf(data, "SSL is disabled, https: not supported!");
    return URG_UNSUPPORTED_PROTOCOL;
#endif /* !USE_SSLEAY */
  }
  else if (strequal(proto, "GOPHER")) {
    if(!data->port)
      data->port = PORT_GOPHER;
    data->remote_port = PORT_GOPHER;
    /* Skip /<item-type>/ in path if present */
    if (isdigit((int)path[1])) {
      ppath = strchr(&path[1], '/');
      if (ppath == NULL)
	ppath = path;
      }
    data->conf |= CONF_GOPHER;
  }
  else if(strequal(proto, "FTP")) {
    char *type;
    if(!data->port)
      data->port = PORT_FTP;
    data->remote_port = PORT_FTP;
    data->conf |= CONF_FTP;

    ppath++; /* don't include the initial slash */

    /* FTP URLs support an extension like ";type=<typecode>" that
       we'll try to get now! */
    type=strstr(ppath, ";type=");
    if(!type) {
      type=strstr(gname, ";type=");
    }
    if(type) {
      char command;
      *type=0;
      command = toupper(type[6]);
      switch(command) {
      case 'A': /* ASCII mode */
	data->conf |= CONF_FTPASCII;
	break;
      case 'D': /* directory mode */
	data->conf |= CONF_FTPLISTONLY;
	break;
      case 'I': /* binary mode */
      default:
	/* switch off ASCII */
	data->conf &= ~CONF_FTPASCII; 
	break;
      }
    }
  }
#ifdef TRY_TELNET
  else if(strequal(proto, "TELNET")) {
    /* telnet testing factory */
    data->conf |= CONF_TELNET;
    if(!data->port)
      data->port = PORT_TELNET;
    data->remote_port = PORT_TELNET;
  }
#endif
  else if (strequal(proto, "DICT")) {
    data->conf |= CONF_DICT;
    if(!data->port)
      data->port = PORT_DICT;
    data->remote_port = PORT_DICT;
  }
  else {
    failf(data, "Unsupported protocol: %s", proto);
    return URG_UNSUPPORTED_PROTOCOL;
  }

  if(data->conf & CONF_NETRC) {
    if(ParseNetrc(data->hostname, ftpuser, ftppasswd)) {
      infof(data, "Couldn't find host %s in the .netrc file, using defaults",
            data->hostname);
    }
    /* weather we failed or not, we don't know which fields that were filled
       in anyway */
    if(!ftpuser[0])
      strcpy(ftpuser, "anonymous");
    if(!ftppasswd[0])
      strcpy(ftppasswd, "curl_by_Daniel.Stenberg@sth.frontec.se");
    if(data->conf & CONF_HTTP) {
      data->conf |= CONF_USERPWD;
    }
  }
  else if(!(data->conf & CONF_USERPWD) &&
	  (data->conf & (CONF_FTP|CONF_HTTP)) ) {
    /* This is a FTP or HTTP URL, and we haven't got the user+password in
       the extra parameter, we will now try to extract the possible
       user+password pair in a string like:
       ftp://user:password@ftp.my.site:8021/README */
    char *ptr=NULL; /* assign to remove possible warnings */
    if(':' == *name) {
      failf(data, "URL malformat: user can't be zero length");
      return URG_URL_MALFORMAT_USER;
    }
    if((1 <= sscanf(name, "%127[^:]:%127[^@]",
		    ftpuser, ftppasswd)) && (ptr=strchr(name, '@'))) {
      name = ++ptr;
      data->conf |= CONF_USERPWD;
    }
    else {
      strcpy(ftpuser, "anonymous");
      strcpy(ftppasswd, "curl_by_Daniel.Stenberg@sth.frontec.se");
    }
  }

  if(!(data->conf & CONF_PROXY)) {
    /* If not connecting via a proxy, extract the port from the URL, if it is
     * there, thus overriding any defaults that might have been set above. */
    tmp = strchr(name, ':');
    if (tmp) {
      *tmp++ = '\0';
      data->port = atoi(tmp);
    }
    
    /* Connect to target host right on */
    if(!(hp = GetHost(data, name))) {
      failf(data, "Couldn't resolv host '%s'", name);
      return URG_COULDNT_RESOLVE_HOST;
    }
  }
  else {
    char *prox_portno;
    char *endofprot;

    /* we use proxy all right, but we wanna know the remote port for SSL
       reasons */
    tmp = strchr(name, ':');
    if (tmp) {
      *tmp++ = '\0'; /* cut off the name there */
      data->remote_port = atoi(tmp);
    }

    /* Daniel Dec 10, 1998:
       We do the proxy host string parsing here. We want the host name and the
       port name. Accept a protocol:// prefix, even though it should just be
       ignored. */

    /* 1. skip the protocol part if present */
    endofprot=strstr(data->proxy, "://");
    if(endofprot) {
      data->proxy = endofprot+3;
    }

    /* allow user to specify proxy.server.com:1080 if desired */
    prox_portno = strchr (data->proxy, ':');
    if (prox_portno) {
      *prox_portno = 0x0; /* cut off number from host name */
      prox_portno ++;
      /* now set the local port number */
      data->port = atoi(prox_portno);
    }

    /* connect to proxy */
    if(!(hp = GetHost(data, data->proxy))) {
      failf(data, "Couldn't resolv proxy '%s'", data->proxy);
      return URG_COULDNT_RESOLVE_PROXY;
    }
  }

  data->firstsocket = socket(AF_INET, SOCK_STREAM, 0);

  memset((char *) &serv_addr, '\0', sizeof(serv_addr));
  memcpy((char *)&(serv_addr.sin_addr), hp->h_addr, hp->h_length);
  serv_addr.sin_family = hp->h_addrtype;

  serv_addr.sin_port = htons(data->port);

  if (connect(data->firstsocket, (struct sockaddr *) &serv_addr,
	      sizeof(serv_addr)) < 0) {
    switch(errno) {
#ifdef ECONNREFUSED
      /* this should be made nicer */
    case ECONNREFUSED:
      failf(data, "Connection refused");
      break;
#endif
#ifdef EINTR
    case EINTR:
      failf(data, "Connection timeouted");
      break;
#endif
    default:
      failf(data, "Can't connect to server: %d", errno);
      break;
    }
    return URG_COULDNT_CONNECT;
  }

  if(data->conf & CONF_PROXYUSERPWD) {
    char authorization[512];
    sprintf(data->buffer, "%s:%s", proxyuser, proxypasswd);
    base64Encode(data->buffer, authorization);

    data->ptr_proxyuserpwd = maprintf("Proxy-authorization: Basic %s\015\012",
				      authorization);
  }
  if(data->conf & (CONF_HTTPS|CONF_HTTP)) {
    if(data->useragent) {
      data->ptr_uagent = maprintf("User-Agent: %s\015\012", data->useragent);
    }
  }


  /* If we are not using a proxy and we want a secure connection,
   * perform SSL initialization & connection now.
   * If using a proxy with https, then we must tell the proxy to CONNECT
   * us to the host we want to talk to.  Only after the connect
   * has occured, can we start talking SSL
   */
   if (data->conf & CONF_HTTPS) {
     if (data->conf & CONF_PROXY) {

        /* OK, now send the connect statment */
        sendf(data->firstsocket, data,
              "CONNECT %s:%d HTTP/1.0\015\012"
              "%s"
	      "%s"
              "\r\n",
              data->hostname, data->remote_port,
              (data->conf&CONF_PROXYUSERPWD)?data->ptr_proxyuserpwd:"",
	      (data->useragent?data->ptr_uagent:"")
              );

        /* wait for the proxy to send us a HTTP/1.0 200 OK header */
	/* Daniel rewrote this part Nov 5 1998 to make it more obvious */
	{
	  int httperror=0;
	  int subversion=0;
	  while(GetLine(data->firstsocket, data->buffer, data)) {
	    if('\r' == data->buffer[0])
	      break; /* end of headers */
	    if(2 == sscanf(data->buffer, "HTTP/1.%d %d",
			   &subversion,
			   &httperror)) {
	      ;
	    }
	  }
	  if(200 != httperror) {
	    if(407 == httperror)
	      /* Added Nov 6 1998 */
	      failf(data, "Proxy requires authorization!");
	    else 
	      failf(data, "Received error code %d from proxy", httperror);
	    return URG_READ_ERROR;
	  }
	}
        infof (data, "Proxy has replied to CONNECT request\n");
     }

      /* now, perform the SSL initialization for this socket */
     if(UrgSSLConnect (data)) {
       return URG_SSL_CONNECT_ERROR;
     }
  }

  now = tvnow(); /* time this *after* the connect is done */
  bytecount = 0;
  
  /* Figure out the ip-number and the first host name it shows: */
  {
    struct in_addr in;
    (void) memcpy(&in.s_addr, *hp->h_addr_list, sizeof (in.s_addr));
    infof(data, "Connected to %s (%s)\n", hp->h_name, inet_ntoa(in));
  }

  if((data->conf&(CONF_FTP|CONF_PROXY)) == CONF_FTP) {
    result = ftp(data, &bytecount, ftpuser, ftppasswd, ppath);
    if(result)
      return result;
  }
#ifdef TRY_TELNET
  /***********************************************************************
   *
   * Telnet.
   *
   * - Require user+password.
   *
   * 1. Negotiation.
   * 2. Read until login: prompt.
   * 3. Send login.
   * 4. Read until password: prompt.
   * 5. Send password.
   * 6. Send extra command (echo ---)
   * 7. Wait for extra command output, when recieived we know we're in.
   * 8. Run command; echo <boundary>
   * 9. Eat everything until the first newline
   * 10. Dump everything
   * 11. If <boundary> is found, abort.
   *
   * Still very experimental.
   *
   **********************************************************************/
   

  else if(data->conf & CONF_TELNET) {

    typedef enum {
      TELNET_CONNECT,
      TELNET_LOGIN,
      TELNET_PASSWD,
      TELNET_INSIDE,
      TELNET_DUMP
    } TelnetState;

#include <arpa/telnet.h>
    unsigned char telopt[] =
    {IAC,WONT,TELOPT_TTYPE,
     IAC,DO, TELOPT_SGA,
     IAC,WONT,TELOPT_XDISPLOC,
     IAC,WONT,TELOPT_NAWS,
     IAC,WONT,TELOPT_OLD_ENVIRON,
     IAC,WONT,TELOPT_NEW_ENVIRON,
     IAC,DONT,TELOPT_ECHO};
    
    TelnetState state=TELNET_CONNECT;

    char *endstring;
    int newlines=0;
    int endlen;
    int endmatch=0;

    swrite(data->firstsocket,telopt,21);   /* kludge some telnet negotiation */

    while (1) {
      unsigned char cmd;
      char *buffer;
      char *ptr;

      sread(data->firstsocket, data->buffer, 1);

      if((unsigned char)data->buffer[0] > 250) {
	cmd = data->buffer[0];
	/* 251 - WILL
	   252 - WONT
	   253 - DO
	   254 - DONT
	   255 - IAC */
	sread(data->firstsocket, data->buffer, 2);
#if 0
	fprintf(stderr, "%u %u %u\n",
		(unsigned char)cmd,
		(unsigned char)data->buffer[0],
		(unsigned char)data->buffer[1]);
#endif
      }
      else {
	buffer = telnetin(data, data->buffer[0]);

	if(state == TELNET_DUMP) {
	  if(newlines) {
	    if(!*buffer) {
	      /* newline indication, decrease */
	      newlines--;
	    }
	  }
	  else {

	    
	    if(data->buffer[0] == endstring[endmatch]) {
	      endmatch++; /* one (more) letter matches */

	      if(endmatch == endlen) {
		/* this is end of data */
		free(endstring);
		break;
	      }
	    }
	    else {
	      if(endmatch) {
		fwrite(endstring, endmatch, 1, stdout);
		endmatch=0;
	      }
	      printf("%c", data->buffer[0]);
	    }
	  }
	}

	/*	fprintf(stderr, "buffer: %s\n", buffer); */

	if((state == TELNET_CONNECT) &&
	   strequal("login: ", buffer)) {
	  infof(data, "Got the login: prompt!\n");
	  sendf(data->firstsocket, data, "%s\r\n", ftpuser); 
	  state=TELNET_LOGIN; /* we have passed login */
	}
	else if((state == TELNET_LOGIN) &&
		strequal("Password: ", buffer)) {
	  infof(data, "Got the Password: prompt!\n");
	  sendf(data->firstsocket, data, "%s\r\n", ftppasswd); 
	  state=TELNET_PASSWD; /* we have passed password */

	  sendf(data->firstsocket, data, "echo ---\r\n"); 
	}
	else if((state == TELNET_PASSWD) &&
		strequal("---", buffer)) {
	  infof(data, "We're logged in!\n");
	  endstring = MakeFormBoundary();

	  sendf(data->firstsocket, data, "%s;echo %s\r\n",
		ppath+1, endstring);
	  state=TELNET_INSIDE;
	}
	else if((state == TELNET_INSIDE) &&
		(ptr = strstr(buffer, ppath+1)) ) {
	  endlen = strlen(endstring);
	  endmatch=0;

	  state = TELNET_DUMP;
	  newlines=1; /* swallow 2 newlines, then go */
	}
      }
    }
  }
#endif
  else if (data->conf & CONF_DICT) {
    int nth;
    char *word;
    char *database = NULL;
    char *strategy = NULL;
    char *nthdef = NULL; /* This is not part of the protocol, but required
			    by RFC 2229 */
    
    if(data->conf & CONF_USERPWD) {
      /* AUTH is missing */
    }

    if (strnequal(path, DICT_MATCH, sizeof(DICT_MATCH)-1) ||
	strnequal(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) ||
	strnequal(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) {
      
      word = strchr(path, ':');
      if (word) {
	word++;
	database = strchr(word, ':');
	if (database) {
	  *database++ = (char)0;
	  strategy = strchr(database, ':');
	  if (strategy) {
	    *strategy++ = (char)0;
	    nthdef = strchr(strategy, ':');
	    if (nthdef) {
	      *nthdef++ = (char)0;
	    }
	  }
	}
      }
      
      if ((word == NULL) || (*word == (char)0)) {
	failf(data, "lookup word is missing\n");
      }
      if ((database == NULL) || (*database == (char)0)) {
	database = "!";
      }
      if ((strategy == NULL) || (*strategy == (char)0)) {
	strategy = ".";
      }
      if ((nthdef == NULL) || (*nthdef == (char)0)) {
	nth = 0;
      } else {
	nth = atoi(nthdef);
      }
      
      sendf(data->firstsocket, data,
	    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\n"
	    "MATCH "
	    "%s "    /* database */
	    "%s "    /* strategy */
	    "%s\n"   /* word */
	    "QUIT\n",
	    
	    database,
	    strategy,
	    word
	    );
    
      result = Download(data, data->firstsocket, -1, FALSE, &bytecount);
      
      if(result)
	return result;
      
    } else if (strnequal(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) ||
	       strnequal(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) ||
	       strnequal(path, DICT_DEFINE3, sizeof(DICT_DEFINE3)-1)) {
      
      word = strchr(path, ':');
      if (word) {
	word++;
	database = strchr(word, ':');
	if (database) {
	  *database++ = (char)0;
	  nthdef = strchr(database, ':');
	  if (nthdef) {
	    *nthdef++ = (char)0;
	  }
	}
      }
      
      if ((word == NULL) || (*word == (char)0)) {
	failf(data, "lookup word is missing\n");
      }
      if ((database == NULL) || (*database == (char)0)) {
	database = "!";
      }
      if ((nthdef == NULL) || (*nthdef == (char)0)) {
	nth = 0;
      } else {
	nth = atoi(nthdef);
      }
      
      sendf(data->firstsocket, data,
	    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\n"
	    "DEFINE "
	    "%s "     /* database */
	    "%s\n"    /* word */
	    "QUIT\n",

	    database,
	    word
	    );
    
      result = Download(data, data->firstsocket, -1, FALSE, &bytecount);
      
      if(result)
	return result;
      
    } else {
      
      ppath = strchr(path, '/');
      if (ppath) {
	int i;
	
	ppath++;
	for (i = 0; (i < URL_MAX_LENGTH) && (ppath[i]); i++) {
	  if (ppath[i] == ':')
	    ppath[i] = ' ';
	}
	sendf(data->firstsocket, data,
	      "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\n"
	      "%s\n"
	      "QUIT\n",
	      ppath);

	result = Download(data, data->firstsocket, -1, FALSE, &bytecount);
      
	if(result)
	  return result;
	
      }
    }
    
    ProgressEnd(data);
  }
  else {
    /* Send the GET line to the HTTP server */

    struct FormData *sendit=NULL;
    int postsize=0;

    if((data->conf&(CONF_HTTP|CONF_UPLOAD)) ==
       (CONF_HTTP|CONF_UPLOAD)) {
      /* enable PUT! */
      data->conf |= CONF_PUT;
    }

    if(data->conf & CONF_USERPWD) {
      char authorization[512];
      sprintf(data->buffer, "%s:%s", ftpuser, ftppasswd);
      base64Encode(data->buffer, authorization);
      data->ptr_userpwd = maprintf( "Authorization: Basic %s\015\012",
				    authorization);
    }
    if(data->conf & CONF_RANGE) {
      data->ptr_rangeline = maprintf("Range: bytes=%s\015\012", data->range);
    }
    if(data->conf & CONF_REFERER) {
      data->ptr_ref = maprintf("Referer: %s\015\012", data->referer);
    }
    if(data->cookie) {
      data->ptr_cookie = maprintf("Cookie: %s\015\012", data->cookie);
    }

    if ((data->conf & CONF_PROXY) && (!(data->conf & CONF_HTTPS)))  {
      /* The path sent to the proxy is in fact the entire URL */
      strncpy(ppath, data->url, URL_MAX_LENGTH-1);
    }
    if(data->conf & CONF_HTTPPOST) {
      /* we must build the whole darned post sequence first, so that we have
	 a size of the whole shebang before we start to send it */
      sendit = getFormData(data->httppost, &postsize);
    }
    do {
      sendf(data->firstsocket, data,
            "%s " /* GET/HEAD/POST/PUT */
            "%s HTTP/1.0\r\n" /* path */
            "%s" /* proxyuserpwd */
            "%s" /* userpwd */
            "%s" /* range */
            "%s" /* user agent */
            "%s" /* cookie */
            "Host: %s\r\n" /* host */
#ifdef MULTIDOC
            "Connection: Keep-Alive\r\n"
#endif
            "Pragma: no-cache\r\n"
            "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
            "%s", /* referer */

            data->conf&CONF_NOBODY?"HEAD":
            (data->conf&(CONF_POST|CONF_HTTPPOST))?"POST":
            (data->conf&CONF_PUT)?"PUT":
            "GET",
            ppath,
            (data->conf&CONF_PROXYUSERPWD)?data->ptr_proxyuserpwd:"",
            (data->conf&CONF_USERPWD)?data->ptr_userpwd:"",
            (data->conf&CONF_RANGE)?data->ptr_rangeline:"",
            (data->useragent && *data->useragent)?data->ptr_uagent:"",
            (data->ptr_cookie?data->ptr_cookie:""), /* Cookie: <data> */
            name, /* host */
            (data->conf&CONF_REFERER)?data->ptr_ref:"" /* Referer: <data> <CRLF> */
            );

      if(data->cookies) {
        struct Cookie *co = cookie_getlist(data->cookies,
                                           name,
                                           ppath,
                                           data->conf&CONF_HTTPS?TRUE:FALSE);
        /* now loop through all cookies that matched */
        while(co) {
          sendf(data->firstsocket, data,
                "Cookie: %s=%s\r\n",
                co->name, co->value);
          co = co->next; /* next cookie please */
        }
        cookie_freelist(co); /* free the cookie list */
      }

      while(data->headers) {
        sendf(data->firstsocket, data,
              "%s\015\012",
              data->headers->header);
        data->headers = data->headers->next;
      }

      if(data->conf&(CONF_POST|CONF_HTTPPOST)) {
        if(data->conf & CONF_POST) {
          /* this is the simple x-www-form-urlencoded style */
          sendf(data->firstsocket, data,
                "Content-Length: %d\015\012"
                "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
                "%s\015\012",
                strlen(data->postfields),
                data->postfields );
        }
        else {
          struct Form form;
          size_t (*storefread)(char *, size_t , size_t , FILE *);
          FILE *in;
          long conf;

          if(FormInit(&form, sendit)) {
            failf(data, "Internal HTTP POST error!\n");
            return URG_HTTP_POST_ERROR;
          }

          storefread = data->fread; /* backup */
          in = data->in; /* backup */
          
          data->fread =
            (size_t (*)(char *, size_t, size_t, FILE *))
            FormReader; /* set the read function to read from the
                           generated form data */
          data->in = (FILE *)&form;

          sendf(data->firstsocket, data,
                "Content-Length: %d\r\n",
                postsize-2);
	
          conf = data->conf;
          data->conf &= ~CONF_NOPROGRESS; /* enable progress meter */
          ProgressInit(data, postsize);

          result = Upload(data, data->firstsocket, &bytecount);

          data->conf = conf; /* restore conf values for the download */
          
          if(result)
            return result;
	
          data->fread = storefread; /* restore */
          data->in = in; /* restore */
#if 0
          /* for hard debugging only */
          while (1) {
            sread(data->firstsocket, data->buffer, 1);
            fprintf(stderr, "*%c", data->buffer[0]);
          }
#endif

	sendf(data->firstsocket, data,
	      "\r\n\r\n");
        }
      }
      else if(data->conf&CONF_PUT) {
        /* Let's PUT the data to the server! */
        long conf;

        if(data->infilesize>0) {
          sendf(data->firstsocket, data,
                "Content-Length: %d\r\n\r\n", /* file size */
                data->infilesize );
        }
        else
          sendf(data->firstsocket, data,
                "\015\012");
        
        conf = data->conf;
        data->conf &= ~CONF_NOPROGRESS; /* enable progress meter */

        ProgressInit(data, data->infilesize);

        result = Upload(data, data->firstsocket, &bytecount);
        
        data->conf = conf;

        if(result)
          return result;

        bytecount=0;
      }
      else {
        sendf(data->firstsocket, data, "\r\n");
      }
      /* HTTP GET/HEAD download: */
      result = Download(data, data->firstsocket, -1, TRUE, &bytecount);

      if(result)
        return result;
      
      ProgressEnd(data);
#ifdef MULTIDOC
      if(data->moredoc) {
        ppath = data->moredoc->path; /* new path to get */
        fclose(data->out);
        if(data->moredoc->store)
          data->out = data->moredoc->store; /* new FILE * to write to */
        else
          data->out = stdout;
        data->moredoc = data->moredoc->next; /* advance */
      }
      else
        break;
    } while (1);
#else
    } while (0); 
#endif
  }
  if(bytecount) {
    double ittook = tvdiff (tvnow(), now);
    infof(data, "%i bytes transfered in %.3lf seconds (%.0lf bytes/sec).\n",
          bytecount, ittook, (double)bytecount/(ittook!=0.0?ittook:1));
  }
  return URG_OK;
}

