Skip to content
url.c 113 KiB
Newer Older
   * could be a hostname, IPv4 address or IPv6 address.
   *************************************************************/
  if((1 == sscanf(conn->host.name, "[%*39[0-9a-fA-F:.]%c", &endbracket)) &&
     (']' == endbracket)) {
    /* this is a RFC2732-style specified IP-address */
    conn->bits.ipv6_ip = TRUE;

    conn->host.name++; /* pass the starting bracket */
    tmp = strchr(conn->host.name, ']');
    *tmp = 0; /* zero terminate */
    tmp++; /* pass the ending bracket */
    if(':' != *tmp)
      tmp = NULL; /* no port number available */
  }
  else
    tmp = strrchr(conn->host.name, ':');

  if (tmp) {
    char *rest;
    unsigned long port;

    port=strtoul(tmp+1, &rest, 10);  /* Port number must be decimal */

    if (rest != (tmp+1) && *rest == '\0') {
      /* The colon really did have only digits after it,
       * so it is either a port number or a mistake */

      if (port > 0xffff) {   /* Single unix standard says port numbers are
                              * 16 bits long */
        failf(data, "Port number too large: %lu", port);
        return CURLE_URL_MALFORMAT;
      }

      *tmp = '\0'; /* cut off the name there */
      conn->remote_port = (unsigned short)port;
    }
  }

  /* Programmatically set password:
   *   - always applies, if available
   *   - takes precedence over the values we just set above
   * so scribble it over the top.
   * User-supplied passwords are assumed not to need unescaping.
   *
   * user_password is set in "inherite initial knowledge' above,
   * so it doesn't have to be set in this block
   */
  if (data->set.userpwd != NULL) {
    /* the name is given, get user+password */
    sscanf(data->set.userpwd,
           "%" MAX_CURL_USER_LENGTH_TXT "[^:]:"
           "%" MAX_CURL_PASSWORD_LENGTH_TXT "[^\n]",
  if (data->set.use_netrc != CURL_NETRC_IGNORED) {
                       user, passwd,
                       data->set.netrc_file)) {
      infof(data, "Couldn't find host %s in the " DOT_CHAR "netrc file, using defaults\n",
      conn->bits.user_passwd = 1; /* enable user+password */
  }

  /* If our protocol needs a password and we have none, use the defaults */
       !conn->bits.user_passwd) {
    conn->user = strdup(CURL_DEFAULT_USER);
    conn->passwd = strdup(CURL_DEFAULT_PASSWORD);
    /* This is the default password, so DON'T set conn->bits.user_passwd */
  }
    /* store user + password, zero-length if not set */
    conn->user = strdup(user);
    conn->passwd = strdup(passwd);
  if(!conn->user || !conn->passwd)
    return CURLE_OUT_OF_MEMORY;
  /*************************************************************
   * Check the current list of connections to see if we can
   * re-use an already existing one or if we have to create a
   * new one.
   *************************************************************/

  /* get a cloned copy of the SSL config situation stored in the
     connection struct */
  if(!Curl_clone_ssl_config(&data->set.ssl, &conn->ssl_config))
    return CURLE_OUT_OF_MEMORY;

  /* reuse_fresh is TRUE if we are told to use a new connection by force, but
     we only acknowledge this option if this is not a re-used connection
     already (which happens due to follow-location or during a HTTP
     authentication phase). */
  if(data->set.reuse_fresh && !data->state.this_is_a_follow)
    reuse = ConnectionExists(data, conn, &conn_temp);

  if(reuse) {
    /*
     * We already have a connection for this, we got the former connection
     * in the conn_temp variable and thus we need to cleanup the one we
     * just allocated before we can move along and use the previously
     * existing one.
     */
    if(old_conn->proxy.rawalloc)
      free(old_conn->proxy.rawalloc);
    /* free the SSL config struct from this connection struct as this was
       allocated in vain and is targeted for destruction */
    Curl_free_ssl_config(&conn->ssl_config);

    conn = conn_temp;        /* use this connection from now on */
    /* get the user+password information from the old_conn struct since it may
     * be new for this request even when we re-use an existing connection */
    conn->bits.user_passwd = old_conn->bits.user_passwd;
    if (conn->bits.user_passwd) {
      /* use the new user namd and password though */
      Curl_safefree(conn->user);
      Curl_safefree(conn->passwd);
      conn->user = old_conn->user;
      conn->passwd = old_conn->passwd;
      old_conn->user = NULL;
      old_conn->passwd = NULL;
    }

    conn->bits.proxy_user_passwd = old_conn->bits.proxy_user_passwd;
    if (conn->bits.proxy_user_passwd) {
      /* use the new proxy user name and proxy password though */
      Curl_safefree(conn->proxyuser);
      Curl_safefree(conn->proxypasswd);
      conn->proxyuser = old_conn->proxyuser;
      conn->proxypasswd = old_conn->proxypasswd;
      old_conn->proxyuser = NULL;
      old_conn->proxypasswd = NULL;
    }
    /* host can change, when doing keepalive with a proxy ! */
    if (conn->bits.httpproxy) {
      free(conn->host.rawalloc);
      conn->host=old_conn->host;
    }

    /* get the newly set value, not the old one */
    conn->bits.no_body = old_conn->bits.no_body;

    if (!conn->bits.httpproxy)
      free(old_conn->host.rawalloc); /* free the newly allocated name buffer */
    free(conn->pathbuffer); /* free the newly allocated path pointer */
    conn->pathbuffer = old_conn->pathbuffer; /* use the old one */
    conn->path = old_conn->path;
Daniel Stenberg's avatar
Daniel Stenberg committed
    conn->bits.reuse = TRUE; /* yes, we're re-using here */
    conn->bits.chunk = FALSE; /* always assume not chunked unless told
                                 otherwise */
    conn->maxdownload = -1;  /* might have been used previously! */
    Curl_safefree(old_conn->user);
    Curl_safefree(old_conn->passwd);
Daniel Stenberg's avatar
Daniel Stenberg committed
    Curl_safefree(old_conn->proxyuser);
    Curl_safefree(old_conn->proxypasswd);
    if(old_conn->bits.rangestringalloc)
      free(old_conn->range);

    free(old_conn);          /* we don't need this anymore */

    /*
     * If we're doing a resumed transfer, we need to setup our stuff
     * properly.
     */
    conn->resume_from = data->set.set_resume_from;
      if (conn->bits.rangestringalloc == TRUE)
        free(conn->range);
      conn->range = aprintf("%" FORMAT_OFF_T "-", conn->resume_from);
      if(!conn->range)
        return CURLE_OUT_OF_MEMORY;

      /* tell ourselves to fetch this range */
      conn->bits.use_range = TRUE;        /* enable range download */
      conn->bits.rangestringalloc = TRUE; /* mark range string allocated */
      /* There is a range, but is not a resume, useful for random ftp access */
      conn->range = strdup(data->set.set_range);
      conn->bits.rangestringalloc = TRUE; /* mark range string allocated */
      conn->bits.use_range = TRUE;        /* enable range download */
    }
    else
      conn->bits.use_range = FALSE; /* disable range download */
    *in_connect = conn;      /* return this instead! */

    infof(data, "Re-using existing connection! (#%ld) with host %s\n",
          conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname);
  }
  else {
    /*
     * This is a brand new connection, so let's store it in the connection
     * cache of ours!
     */
    ConnectionStore(data, conn);
  }

  /* Continue connectdata initialization here.
   * Inherit the proper values from the urldata struct AFTER we have arranged
   * the persistant conncetion stuff */
  conn->fread = data->set.fread;
  conn->fread_in = data->set.in;

  conn->bits.upload_chunky =
    ((conn->protocol&PROT_HTTP) &&
     data->set.upload &&
     (data->set.infilesize == -1) &&
     (data->set.httpversion != CURL_HTTP_VERSION_1_0))?
    /* HTTP, upload, unknown file size and not HTTP 1.0 */
    TRUE:
  /* else, no chunky upload */
  FALSE;

  /*************************************************************
   * Set timeout if that is being used, and we're not using an asynchronous
   * name resolve.
   *************************************************************/
  if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) {
    /*************************************************************
     * Set signal handler to catch SIGALRM
     * Store the old value to be able to set it back later!
     *************************************************************/

#ifdef HAVE_SIGACTION
    struct sigaction sigact;
    sigaction(SIGALRM, NULL, &sigact);
    keep_sigact = sigact;
    keep_copysig = TRUE; /* yes, we have a copy */
    sigact.sa_handler = alarmfunc;
#ifdef SA_RESTART
    /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
    sigact.sa_flags &= ~SA_RESTART;
#endif
    /* now set the new struct */
    sigaction(SIGALRM, &sigact, NULL);
#else /* HAVE_SIGACTION */
    /* no sigaction(), revert to the much lamer signal() */
#ifdef HAVE_SIGNAL
    keep_sigact = signal(SIGALRM, alarmfunc);
#endif /* HAVE_SIGACTION */
    /* We set the timeout on the name resolving phase first, separately from
     * the download/upload part to allow a maximum time on everything. This is
     * a signal-based timeout, why it won't work and shouldn't be used in
     * multi-threaded environments. */

#ifdef HAVE_ALARM
    /* alarm() makes a signal get sent when the timeout fires off, and that
    prev_alarm = alarm(data->set.connecttimeout?
                       data->set.connecttimeout:
                       data->set.timeout);
    /* We can expect the conn->created time to be "now", as that was just
       recently set in the beginning of this function and nothing slow
       has been done since then until now. */
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
  /*************************************************************
   * Resolve the name of the server or proxy
   *************************************************************/
  if(conn->bits.reuse) {
    /* re-used connection, no resolving is necessary */
    hostaddr = NULL;
    /* we'll need to clear conn->dns_entry later in Curl_disconnect() */

    if (conn->bits.httpproxy)
      fix_hostname(conn, &conn->host);
    /* set a pointer to the hostname we display */
    fix_hostname(conn, &conn->host);

    if(!data->change.proxy || !*data->change.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. */
      conn->port =  conn->remote_port; /* it is the same port */

      /* Resolve target host right on */
      rc = Curl_resolv(conn, conn->host.name, (int)conn->port, &hostaddr);
      if(rc == CURLRESOLV_PENDING)
        *async = TRUE;
      else if(!hostaddr) {
        failf(data, "Couldn't resolve host '%s'", conn->host.dispname);
        result =  CURLE_COULDNT_RESOLVE_HOST;
        /* don't return yet, we need to clean up the timeout first */
      }
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    else {
      /* This is a proxy that hasn't been resolved yet. */
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed
      /* IDN-fix the proxy name */
      rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &hostaddr);
      if(rc == CURLRESOLV_PENDING)
        *async = TRUE;

      else if(!hostaddr) {
        failf(data, "Couldn't resolve proxy '%s'", conn->proxy.dispname);
        result = CURLE_COULDNT_RESOLVE_PROXY;
        /* don't return yet, we need to clean up the timeout first */
      }
#if defined(HAVE_ALARM) && defined(SIGALRM) && !defined(USE_ARES)
  if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) {
#ifdef HAVE_SIGACTION
    if(keep_copysig) {
      /* we got a struct as it looked before, now put that one back nice
         and clean */
      sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
    }
#else
#ifdef HAVE_SIGNAL
    /* restore the previous SIGALRM handler */
    signal(SIGALRM, keep_sigact);
#endif
    /* switch back the alarm() to either zero or to what it was before minus
       the time we spent until now! */
    if(prev_alarm) {
      /* there was an alarm() set before us, now put it back */
      unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);
      unsigned long alarm_set;

      /* the alarm period is counted in even number of seconds */
      alarm_set = prev_alarm - elapsed_ms/1000;
      if(!alarm_set ||
         ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
        /* if the alarm time-left reached zero or turned "negative" (counted
           with unsigned values), we should fire off a SIGALRM here, but we
           won't, and zero would be to switch it off so we never set it to
           less than 1! */
        alarm(1);
        result = CURLE_OPERATION_TIMEOUTED;
        failf(data, "Previous alarm fired off!");
      }
      else
        alarm((unsigned int)alarm_set);

  return result;
}

/* SetupConnection() should be called after the name resolve initiated in
 * CreateConnection() is all done.
 */
static CURLcode SetupConnection(struct connectdata *conn,
                                struct Curl_dns_entry *hostaddr)
{
  struct SessionHandle *data = conn->data;
  CURLcode result=CURLE_OK;

  Curl_pgrsTime(data, TIMER_NAMELOOKUP);

  if(conn->protocol & PROT_FILE)
    /* There's nothing in this function to setup if we're only doing
       a file:// transfer */
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed
  /*************************************************************
   * Send user-agent to HTTP proxies even if the target protocol
   * isn't HTTP.
   *************************************************************/
  if((conn->protocol&PROT_HTTP) ||
     (data->change.proxy && *data->change.proxy)) {
        aprintf("User-Agent: %s\015\012", data->set.useragent);
      if(!conn->allocptr.uagent)
        return CURLE_OUT_OF_MEMORY;
  conn->bytecount = 0;
  conn->headerbytecount = 0;
  if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* Connect only if not already connected! */
    result = ConnectPlease(conn, hostaddr, &connected);

      result = Curl_protocol_connect(conn);
      if(CURLE_OK == result)
        conn->bits.tcpconnect = TRUE;
    }
    else
      conn->bits.tcpconnect = FALSE;

Daniel Stenberg's avatar
Daniel Stenberg committed
    if(CURLE_OK != result)
      return result;
Daniel Stenberg's avatar
Daniel Stenberg committed
  }
  else {
    Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
Daniel Stenberg's avatar
Daniel Stenberg committed
  }

  conn->now = Curl_tvnow(); /* time this *after* the connect is done, we
                               set this here perhaps a second time */
Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef __EMX__
  /* 20000330 mgs
   * the check is quite a hack...
   * we're calling _fsetmode to fix the problem with fwrite converting newline
   * characters (you get mangled text files, and corrupted binary files when
   * you download to stdout and redirect it to a file). */

  if ((data->set.out)->_handle == NULL) {
    _fsetmode(stdout, "b");
  }
#endif

CURLcode Curl_connect(struct SessionHandle *data,
                      struct connectdata **in_connect,
                      bool *asyncp)
{
  CURLcode code;
  *asyncp = FALSE; /* assume synchronous resolves by default */
  /* call the stuff that needs to be called */
  code = CreateConnection(data, in_connect, &dns, asyncp);

  if(CURLE_OK == code) {
    /* no error */
    if(dns || !*asyncp)
      /* If an address is available it means that we already have the name
         resolved, OR it isn't async.
         If so => continue connecting from here */
      code = SetupConnection(*in_connect, dns);
    /* else
         response will be received and treated async wise */
  }
  if(CURLE_OK != code) {
    /* We're not allowed to return failure with memory left allocated
       in the connectdata struct, free those here */
    if(*in_connect) {
      Curl_disconnect(*in_connect); /* close the connection */
      *in_connect = NULL;           /* return a NULL */
/* Call this function after Curl_connect() has returned async=TRUE and
   then a successful name resolve has been received */
CURLcode Curl_async_resolved(struct connectdata *conn)
{
#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \
    defined(USE_THREADING_GETADDRINFO)
  CURLcode code = SetupConnection(conn, conn->async.dns);

  if(code)
    /* We're not allowed to return failure with memory left allocated
       in the connectdata struct, free those here */
    Curl_disconnect(conn); /* close the connection */

  return code;
#else
  (void)conn;
  return CURLE_OK;
#endif
}


CURLcode Curl_done(struct connectdata **connp,
                   CURLcode status) /* an error if this is called after an
                                       error was detected */
{
  CURLcode result;
  struct connectdata *conn = *connp;
  struct SessionHandle *data=conn->data;
  /* cleanups done even if the connection is re-used */
  if(conn->bits.rangestringalloc) {
    free(conn->range);
    conn->bits.rangestringalloc = FALSE;
  }

  if(conn->dns_entry) {
    Curl_resolv_unlock(data, conn->dns_entry); /* done with this */
    conn->dns_entry = NULL;
  }

  /* Cleanup possible redirect junk */
  if(conn->newurl) {
    free(conn->newurl);
    conn->newurl = NULL;
  }
  /* this calls the protocol-specific function pointer previously set */
  if(conn->curl_done)
    result = conn->curl_done(conn, status);
  else
    result = CURLE_OK;

  Curl_pgrsDone(conn); /* done with the operation */
  /* if data->set.reuse_forbid is TRUE, it means the libcurl client has
     forced us to close this no matter what we think.
     if conn->bits.close is TRUE, it means that the connection should be
     closed in spite of all our efforts to be nice, due to protocol
     restrictions in our or the server's end */
  if(data->set.reuse_forbid || conn->bits.close) {
    CURLcode res2;
    res2 = Curl_disconnect(conn); /* close the connection */

    *connp = NULL; /* to make the caller of this function better detect that
                      this was actually killed here */

    /* If we had an error already, make sure we return that one. But
       if we got a new error, return that. */
    if(!result && res2)
      result = res2;
  }
    infof(data, "Connection #%ld to host %s left intact\n",
          conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname);
CURLcode Curl_do(struct connectdata **connp)
  CURLcode result=CURLE_OK;
  struct connectdata *conn = *connp;
  struct SessionHandle *data=conn->data;
  conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to use */
    /* generic protocol-specific function pointer set in curl_connect() */
    result = conn->curl_do(conn);

    /* This was formerly done in transfer.c, but we better do it here */
    if((CURLE_SEND_ERROR == result) && conn->bits.reuse) {
      /* This was a re-use of a connection and we got a write error in the
       * DO-phase. Then we DISCONNECT this connection and have another attempt
       * to CONNECT and then DO again! The retry cannot possibly find another
       * connection to re-use, since we only keep one possible connection for
       * each.  */

      infof(data, "Re-used connection seems dead, get a new one\n");

      conn->bits.close = TRUE; /* enforce close of this connection */
      result = Curl_done(&conn, result); /* we are so done with this */
      /* conn may no longer be a good pointer */
      if(CURLE_OK == result) {
        /* Now, redo the connect and get a new connection */
        result = Curl_connect(data, connp, &async);
        if(CURLE_OK == result) {
          /* We have connected or sent away a name resolve query fine */

          conn = *connp; /* setup conn to again point to something nice */
          if(async) {
            /* Now, if async is TRUE here, we need to wait for the name
               to resolve */
            result = Curl_wait_for_resolv(conn, NULL);
            if(result)
              return result;
            /* Resolved, continue with the connection */
            result = Curl_async_resolved(conn);
          /* ... finally back to actually retry the DO phase */
CURLcode Curl_do_more(struct connectdata *conn)
{
  CURLcode result=CURLE_OK;

  if(conn->curl_do_more)
    result = conn->curl_do_more(conn);

  return result;
}

static bool safe_strequal(char* str1, char* str2)
{
  if(str1 && str2)
    /* both pointers point to something then compare them */
    return strequal(str1, str2);
  else
    /* if both pointers are NULL then treat them as equal */
    return (!str1 && !str2);
}

bool
Curl_ssl_config_matches(struct ssl_config_data* data,
                        struct ssl_config_data* needle)
{
  if((data->version == needle->version) &&
     (data->verifypeer == needle->verifypeer) &&
     (data->verifyhost == needle->verifyhost) &&
     safe_strequal(data->CApath, needle->CApath) &&
     safe_strequal(data->CAfile, needle->CAfile) &&
     safe_strequal(data->random_file, needle->random_file) &&
     safe_strequal(data->egdsocket, needle->egdsocket) &&
     safe_strequal(data->cipher_list, needle->cipher_list))
    return TRUE;
  return FALSE;
bool
Curl_clone_ssl_config(struct ssl_config_data *source,
                      struct ssl_config_data *dest)
  dest->verifyhost = source->verifyhost;
  dest->verifypeer = source->verifypeer;
  dest->version = source->version;
  if(source->CAfile) {
    dest->CAfile = strdup(source->CAfile);
    if(!dest->CAfile)
      return FALSE;
  if(source->CApath) {
    dest->CApath = strdup(source->CApath);
    if(!dest->CApath)
      return FALSE;
  if(source->cipher_list) {
    dest->cipher_list = strdup(source->cipher_list);
    if(!dest->cipher_list)
      return FALSE;
  if(source->egdsocket) {
    dest->egdsocket = strdup(source->egdsocket);
    if(!dest->egdsocket)
      return FALSE;
  if(source->random_file) {
    dest->random_file = strdup(source->random_file);
    if(!dest->random_file)
      return FALSE;
void Curl_free_ssl_config(struct ssl_config_data* sslc)
{
  if(sslc->CAfile)
    free(sslc->CAfile);

  if(sslc->CApath)
    free(sslc->CApath);

  if(sslc->cipher_list)
    free(sslc->cipher_list);

  if(sslc->egdsocket)
    free(sslc->egdsocket);

  if(sslc->random_file)
    free(sslc->random_file);
}