Newer
Older
newest = malloc(newlen+1); /* get memory for this */
if(newest) {
strcpy_url(newest, newurl); /* create a space-free URL */
free(newurl); /* that was no good */
newurl = newest; /* use this instead now */
}
}
}
if(data->change.url_alloc)
free(data->change.url);
else
data->change.url_alloc = TRUE; /* the URL is allocated */
data->change.url = newurl;
newurl = NULL; /* don't free! */
infof(data, "Issue another request to this URL: '%s'\n", data->change.url);
/*
* We get here when the HTTP code is 300-399 (and 401). We need to perform
* differently based on exactly what return code there was.
* News from 7.10.6: we can also get here on a 401 or 407, in case we act on
* a HTTP (proxy-) authentication scheme other than Basic.
*/
switch(data->info.httpcode) {
/* 401 - Act on a www-authentication, we keep on moving and do the
Authorization: XXXX header in the HTTP request code snippet */
/* 407 - Act on a proxy-authentication, we keep on moving and do the
Proxy-Authorization: XXXX header in the HTTP request code snippet */
/* 300 - Multiple Choices */
/* 306 - Not used */
/* 307 - Temporary Redirect */
default: /* for all above (and the unknown ones) */
/* Some codes are explicitly mentioned since I've checked RFC2616 and they
* seem to be OK to POST to.
*/
break;
case 301: /* Moved Permanently */
/* (quote from RFC2616, section 10.3.2):
* Note: When automatically redirecting a POST request after receiving a
* 301 status code, some existing HTTP/1.0 user agents will erroneously
* change it into a GET request.
*
* ----
*
* Warning: Because most of importants user agents do this obvious RFC2616
* violation, many webservers expect this misbehavior. So these servers
* often answers to a POST request with an error page. To be sure that
* libcurl gets the page that most user agents would get, libcurl has to
* force GET:
*/
if( data->set.httpreq == HTTPREQ_POST
|| data->set.httpreq == HTTPREQ_POST_FORM) {
infof(data,
"Violate RFC 2616/10.3.2 and switch from POST to GET\n");
data->set.httpreq = HTTPREQ_GET;
}
break;
case 302: /* Found */
/* (From 10.3.3)
Note: RFC 1945 and RFC 2068 specify that the client is not allowed
to change the method on the redirected request. However, most
existing user agent implementations treat 302 as if it were a 303
response, performing a GET on the Location field-value regardless
of the original request method. The status codes 303 and 307 have
been added for servers that wish to make unambiguously clear which
kind of reaction is expected of the client.
(From 10.3.4)
Note: Many pre-HTTP/1.1 user agents do not understand the 303
status. When interoperability with such clients is a concern, the
302 status code may be used instead, since most user agents react
to a 302 response as described here for 303.
*/
case 303: /* See Other */
/* Disable both types of POSTs, since doing a second POST when
* following isn't what anyone would want! */
if(data->set.httpreq != HTTPREQ_GET) {
data->set.httpreq = HTTPREQ_GET; /* enforce GET request */
infof(data, "Disables POST, goes with %s\n",
Daniel Stenberg
committed
data->set.opt_no_body?"HEAD":"GET");
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
}
break;
case 304: /* Not Modified */
/* 304 means we did a conditional request and it was "Not modified".
* We shouldn't get any Location: header in this response!
*/
break;
case 305: /* Use Proxy */
/* (quote from RFC2616, section 10.3.6):
* "The requested resource MUST be accessed through the proxy given
* by the Location field. The Location field gives the URI of the
* proxy. The recipient is expected to repeat this single request
* via the proxy. 305 responses MUST only be generated by origin
* servers."
*/
break;
}
Curl_pgrsTime(data, TIMER_REDIRECT);
Curl_pgrsResetTimes(data);
return CURLE_OK;
}
static CURLcode
Curl_connect_host(struct SessionHandle *data,
struct connectdata **conn)
{
CURLcode res = CURLE_OK;
int urlchanged = FALSE;
do {
bool async;
bool protocol_done=TRUE; /* will be TRUE always since this is only used
within the easy interface */
Curl_pgrsTime(data, TIMER_STARTSINGLE);
data->change.url_changed = FALSE;
res = Curl_connect(data, conn, &async, &protocol_done);
if((CURLE_OK == res) && async) {
/* Now, if async is TRUE here, we need to wait for the name
to resolve */
res = Curl_wait_for_resolv(*conn, NULL);
if(CURLE_OK == res)
/* Resolved, continue with the connection */
res = Curl_async_resolved(*conn, &protocol_done);
else
/* if we can't resolve, we kill this "connection" now */
(void)Curl_disconnect(*conn);
}
if(res)
break;
/* If a callback (or something) has altered the URL we should use within
the Curl_connect(), we detect it here and act as if we are redirected
to the new URL */
urlchanged = data->change.url_changed;
if ((CURLE_OK == res) && urlchanged) {
res = Curl_done(conn, res);
if(CURLE_OK == res) {
char *gotourl = strdup(data->change.url);
Daniel Stenberg
committed
res = Curl_follow(data, gotourl, FALSE);
if(res)
free(gotourl);
}
}
} while (urlchanged && res == CURLE_OK);
return res;
}
/* Returns TRUE and sets '*url' if a request retry is wanted */
bool Curl_retry_request(struct connectdata *conn,
char **url)
{
bool retry = FALSE;
if((conn->keep.bytecount+conn->headerbytecount == 0) &&
Daniel Stenberg
committed
conn->bits.reuse &&
!conn->bits.no_body) {
/* We got no data, we attempted to re-use a connection and yet we want a
"body". This might happen if the connection was left alive when we were
done using it before, but that was closed when we wanted to read from
it again. Bad luck. Retry the same request on a fresh connect! */
infof(conn->data, "Connection died, retrying a fresh connect\n");
*url = strdup(conn->data->change.url);
conn->bits.close = TRUE; /* close this connection */
conn->bits.retry = TRUE; /* mark this as a connection we're about
to retry. Marking it this way should
prevent i.e HTTP transfers to return
error just because nothing has been
transfered! */
retry = TRUE;
}
return retry;
}
/*
* Curl_perform() is the internal high-level function that gets called by the
* external curl_easy_perform() function. It inits, performs and cleans up a
* single file transfer.
*/
CURLcode Curl_perform(struct SessionHandle *data)
{
CURLcode res;
CURLcode res2;
struct connectdata *conn=NULL;
char *newurl = NULL; /* possibly a new URL to follow to! */
Daniel Stenberg
committed
bool retry = FALSE;
data->state.used_interface = Curl_if_easy;
res = Curl_pretransfer(data);
if(res)
return res;
/*
* It is important that there is NO 'return' from this function at any other
* place than falling down to the end of the function! This is because we
* have cleanup stuff that must be done before we get back, and that is only
* performed after this do-while loop.
*/
res = Curl_connect_host(data, &conn); /* primary connection */
if(res == CURLE_OK) {
if (data->set.source_url) /* 3rd party transfer */
res = Curl_second_connect(conn);
else
conn->sec_conn = NULL;
}
if(res == CURLE_OK) {
bool do_done;
Daniel Stenberg
committed
if(data->set.connect_only) {
/* keep connection open for application to use the socket */
conn->bits.close = FALSE;
res = Curl_done(&conn, CURLE_OK);
break;
}
res = Curl_do(&conn, &do_done);
/* for non 3rd party transfer only */
if(res == CURLE_OK && !data->set.source_url) {
res = Transfer(conn); /* now fetch that URL please */
if(res == CURLE_OK) {
retry = Curl_retry_request(conn, &newurl);
if(!retry)
/*
* We must duplicate the new URL here as the connection data may
* be free()ed in the Curl_done() function.
*/
newurl = conn->newurl?strdup(conn->newurl):NULL;
}
Daniel Stenberg
committed
else {
/* The transfer phase returned error, we mark the connection to get
* closed to prevent being re-used. This is becasue we can't
* possibly know if the connection is in a good shape or not now. */
conn->bits.close = TRUE;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) {
Daniel Stenberg
committed
/* if we failed anywhere, we must clean up the secondary socket if
it was used */
Daniel Stenberg
committed
sclose(conn->sock[SECONDARYSOCKET]);
Daniel Stenberg
committed
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
Daniel Stenberg
committed
}
}
Daniel Stenberg
committed
/* Always run Curl_done(), even if some of the previous calls
failed, but return the previous (original) error code */
Daniel Stenberg
committed
res2 = Curl_done(&conn, res);
Daniel Stenberg
committed
if(CURLE_OK == res)
res = res2;
Daniel Stenberg
committed
else
/* Curl_do() failed, clean up left-overs in the done-call */
Daniel Stenberg
committed
res2 = Curl_done(&conn, res);
Daniel Stenberg
committed
/*
* Important: 'conn' cannot be used here, since it may have been closed
* in 'Curl_done' or other functions.
*/
if((res == CURLE_OK) && newurl) {
Daniel Stenberg
committed
res = Curl_follow(data, newurl, retry);
if(CURLE_OK == res) {
newurl = NULL;
continue;
}
}
break; /* it only reaches here when this shouldn't loop */
} while(1); /* loop if Location: */
Daniel Stenberg
committed
if(newurl)
free(newurl);
Daniel Stenberg
committed
if(res && !data->state.errorbuf) {
/*
* As an extra precaution: if no error string has been set and there was
* an error, use the strerror() string or if things are so bad that not
* even that is good, set a bad string that mentions the error code.
*/
Daniel Stenberg
committed
if(!str)
failf(data, "unspecified error %d", (int)res);
else
failf(data, "%s", str);
}
/* run post-transfer uncondionally, but don't clobber the return code if
we already have an error code recorder */
res2 = Curl_posttransfer(data);
if(!res && res2)
res = res2;
return res;
}
/*
* Curl_Transfer() is called to setup some basic properties for the upcoming
* transfer.
*/
Curl_Transfer(struct connectdata *c_conn, /* connection data */
int sockindex, /* socket index to read from or -1 */
curl_off_t size, /* -1 if unknown at this point */
bool getheader, /* TRUE if header parsing is wanted */
curl_off_t *bytecountp, /* return number of bytes read or NULL */
Daniel Stenberg
committed
int writesockindex, /* socket index to write to, it may very
well be the same we read from. -1
disables */
curl_off_t *writecountp /* return number of bytes written or
NULL */
)
{
struct connectdata *conn = (struct connectdata *)c_conn;
if(!conn)
return CURLE_BAD_FUNCTION_ARGUMENT;
curlassert((sockindex <= 1) && (sockindex >= -1));
/* now copy all input parameters */
conn->sockfd = sockindex==-1?
CURL_SOCKET_BAD:conn->sock[sockindex];
conn->bits.getheader = getheader;
conn->bytecountp = bytecountp;
conn->writesockfd = writesockindex==-1?
CURL_SOCKET_BAD:conn->sock[writesockindex];
conn->writebytecountp = writecountp;
return CURLE_OK;
}
/*
* Curl_second_connect() makes the secondary connection (used for 3rd party
* FTP transfers).
*/
CURLcode Curl_second_connect(struct connectdata *conn)
{
CURLcode status = CURLE_OK;
struct SessionHandle *data = conn->data;
struct connectdata *sec_conn = NULL; /* secondary connection */
bool backup_reuse_fresh = data->set.reuse_fresh;
char *backup_userpwd = data->set.userpwd;
if(data->change.url_alloc)
free(data->change.url);
data->change.url_alloc = FALSE;
data->change.url = data->set.source_url;
/* We must never actually alter 'data->set' properties, so we restore the
backed up values afterwards! */
Daniel Stenberg
committed
/* if both remote hosts are the same host - create new connection */
if (strequal(conn->host.dispname, data->set.source_host))
Daniel Stenberg
committed
data->set.reuse_fresh = TRUE;
data->set.userpwd = data->set.source_userpwd;
/* secondary connection */
status = Curl_connect_host(data, &sec_conn);
if(CURLE_OK == status) {
Daniel Stenberg
committed
sec_conn->sec_conn = NULL; /* important if re-using existing connection
to prevent loop */
sec_conn->data = data;
conn->sec_conn = sec_conn;
}
data->set.reuse_fresh = backup_reuse_fresh;
data->set.userpwd = backup_userpwd;
Daniel Stenberg
committed
return status;
}