* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
*
***************************************************************************/
-#include "setup.h"
-
-/* -- WIN32 approved -- */
-#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <errno.h>
+#include "curl_setup.h"
#include "strtoofft.h"
#include "strequal.h"
#include "rawstr.h"
-#ifdef WIN32
-#include <time.h>
-#include <io.h>
-#else
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
-#ifdef HAVE_SYS_TIME_H
-#include <sys/time.h>
-#endif
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#error "We can't compile without socket() support!"
#endif
-#endif /* WIN32 */
-
#include "urldata.h"
#include <curl/curl.h>
#include "netrc.h"
#include "http.h"
#include "url.h"
#include "getinfo.h"
-#include "sslgen.h"
+#include "vtls/vtls.h"
#include "http_digest.h"
-#include "http_ntlm.h"
+#include "curl_ntlm.h"
#include "http_negotiate.h"
#include "share.h"
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
-#define CURL_TIMEOUT_EXPECT_100 1000 /* counting ms here */
-
/*
* This function will call the read callback to fill our buffer with data
* to upload.
#ifdef CURL_DOES_CONVERSIONS
bool sending_http_headers = FALSE;
- if((conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) &&
- (data->state.proto.http->sending == HTTPSEND_REQUEST)) {
- /* We're sending the HTTP request headers, not the data.
- Remember that so we don't re-translate them into garbage. */
- sending_http_headers = TRUE;
+ if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) {
+ const struct HTTP *http = data->req.protop;
+
+ if(http->sending == HTTPSEND_REQUEST)
+ /* We're sending the HTTP request headers, not the data.
+ Remember that so we don't re-translate them into garbage. */
+ sending_http_headers = TRUE;
}
#endif
return CURLE_ABORTED_BY_CALLBACK;
}
else if(nread == CURL_READFUNC_PAUSE) {
- struct SingleRequest *k = &data->req;
- /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */
- k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */
- if(data->req.upload_chunky) {
- /* Back out the preallocation done above */
- data->req.upload_fromhere -= (8 + 2);
+
+ if(conn->handler->flags & PROTOPT_NONETWORK) {
+ /* protocols that work without network cannot be paused. This is
+ actually only FILE:// just now, and it can't pause since the transfer
+ isn't done using the "normal" procedure. */
+ failf(data, "Read callback asked for PAUSE when not supported!");
+ return CURLE_READ_ERROR;
+ }
+ else {
+ struct SingleRequest *k = &data->req;
+ /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */
+ k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */
+ if(data->req.upload_chunky) {
+ /* Back out the preallocation done above */
+ data->req.upload_fromhere -= (8 + 2);
+ }
+ *nreadp = 0;
}
- *nreadp = 0;
return CURLE_OK; /* nothing was read */
}
else if((size_t)nread > buffersize) {
const char *endofline_native;
const char *endofline_network;
int hexlen;
+
+ if(
#ifdef CURL_DO_LINEEND_CONV
- if((data->set.crlf) || (data->set.prefer_ascii)) {
-#else
- if(data->set.crlf) {
-#endif /* CURL_DO_LINEEND_CONV */
+ (data->set.prefer_ascii) ||
+#endif
+ (data->set.crlf)) {
/* \n will become \r\n later on */
endofline_native = "\n";
endofline_network = "\x0a";
strlen(endofline_network));
#ifdef CURL_DOES_CONVERSIONS
- CURLcode res;
+ CURLcode result;
int length;
if(data->set.prefer_ascii) {
/* translate the protocol and data */
/* just translate the protocol portion */
length = strlen(hexbuffer);
}
- res = Curl_convert_to_network(data, data->req.upload_fromhere, length);
+ result = Curl_convert_to_network(data, data->req.upload_fromhere, length);
/* Curl_convert_to_network calls failf if unsuccessful */
- if(res)
- return(res);
+ if(result)
+ return(result);
#endif /* CURL_DOES_CONVERSIONS */
if((nread - hexlen) == 0)
}
#ifdef CURL_DOES_CONVERSIONS
else if((data->set.prefer_ascii) && (!sending_http_headers)) {
- CURLcode res;
- res = Curl_convert_to_network(data, data->req.upload_fromhere, nread);
+ CURLcode result;
+ result = Curl_convert_to_network(data, data->req.upload_fromhere, nread);
/* Curl_convert_to_network calls failf if unsuccessful */
- if(res != CURLE_OK)
- return(res);
+ if(result)
+ return result;
}
#endif /* CURL_DOES_CONVERSIONS */
else {
/* If no CURLOPT_READFUNCTION is used, we know that we operate on a
given FILE * stream and we can actually attempt to rewind that
- ourself with fseek() */
+ ourselves with fseek() */
if(data->set.fread_func == (curl_read_callback)fread) {
if(-1 != fseek(data->set.in, 0, SEEK_SET))
/* successful rewind */
/* in the case of libssh2, we can never be really sure that we have emptied
its internal buffers so we MUST always try until we get EAGAIN back */
return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) ||
+#if defined(USE_NGHTTP2)
+ Curl_ssl_data_pending(conn, FIRSTSOCKET) ||
+ /* For HTTP/2, we may read up everything including responde body
+ with header fields in Curl_http_readwrite_headers. If no
+ content-length is provided, curl waits for the connection
+ close, which we emulate it using conn->proto.httpc.closed =
+ TRUE. The thing is if we read everything, then http2_recv won't
+ be called and we cannot signal the HTTP/2 stream has closed. As
+ a workaround, we return nonzero here to call http2_recv. */
+ ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion == 20 &&
+ conn->proto.httpc.closed);
+#else
Curl_ssl_data_pending(conn, FIRSTSOCKET);
+#endif
}
static void read_rewind(struct connectdata *conn,
}
DEBUGF(infof(conn->data,
- "Buffer after stream rewind (read_pos = %zu): [%s]",
+ "Buffer after stream rewind (read_pos = %zu): [%s]\n",
conn->read_pos, buf));
}
#endif
*didwhat |= KEEP_RECV;
/* indicates data of zero size, i.e. empty file */
- is_empty_data = (bool)((nread == 0) && (k->bodywrites == 0));
+ is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE;
/* NUL terminate, allowing string ops to be used */
if(0 < nread || is_empty_data) {
/* We've stopped dealing with input, get out of the do-while loop */
if(nread > 0) {
- if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) {
+ if(Curl_multi_pipeline_enabled(conn->data->multi)) {
infof(data,
"Rewinding stream by : %zd"
" bytes on url %s (zero-length body)\n",
is non-headers. */
if(k->str && !k->header && (nread > 0 || is_empty_data)) {
-
#ifndef CURL_DISABLE_HTTP
if(0 == k->bodywrites && !is_empty_data) {
/* These checks are only made the first time we are about to
write a piece of the body */
- if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) {
+ if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) {
/* HTTP-only checks */
if(data->req.newurl) {
if(data->state.resume_from && !k->content_range &&
(data->set.httpreq==HTTPREQ_GET) &&
!k->ignorebody) {
+
+ if(k->size == data->state.resume_from) {
+ /* The resume point is at the end of file, consider this fine
+ even if it doesn't allow resume from here. */
+ infof(data, "The entire document is already downloaded");
+ connclose(conn, "already downloaded");
+ /* Abort download */
+ k->keepon &= ~KEEP_RECV;
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
/* we wanted to resume a download, although the server doesn't
* seem to support this and we did this with a GET (if it
* wasn't a GET we did a POST or PUT resume) */
if(!Curl_meets_timecondition(data, k->timeofdoc)) {
*done = TRUE;
+ /* We're simulating a http 304 from server so we return
+ what should have been returned from the server */
+ data->info.httpcode = 304;
+ infof(data, "Simulate a HTTP 304 response!\n");
+ /* we abort the transfer before it is completed == we ruin the
+ re-use ability. Close the connection */
+ connclose(conn, "Simulated 304 handling");
return CURLE_OK;
}
} /* we have a time condition */
- } /* this is HTTP */
+ } /* this is HTTP or RTSP */
} /* this is the first time we write a body part */
#endif /* CURL_DISABLE_HTTP */
+
k->bodywrites++;
/* pass data to the debug function before it gets "dechunked" */
failf(data, "Failed writing data");
return CURLE_WRITE_ERROR;
}
- failf(data, "Received problem %d in the chunky parser", (int)res);
+ failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res));
return CURLE_RECV_ERROR;
}
else if(CHUNKE_STOP == res) {
dataleft = conn->chunk.dataleft;
if(dataleft != 0) {
- infof(conn->data, "Leftovers after chunking: %zu bytes", dataleft);
- if(conn->data->multi &&
- Curl_multi_canPipeline(conn->data->multi)) {
+ infof(conn->data, "Leftovers after chunking: %zu bytes\n",
+ dataleft);
+ if(Curl_multi_pipeline_enabled(conn->data->multi)) {
/* only attempt the rewind if we truly are pipelining */
infof(conn->data, "Rewinding %zu bytes\n",dataleft);
read_rewind(conn, dataleft);
excess = (size_t)(k->bytecount + nread - k->maxdownload);
if(excess > 0 && !k->ignorebody) {
- if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) {
+ if(Curl_multi_pipeline_enabled(conn->data->multi)) {
/* The 'excess' amount below can't be more than BUFSIZE which
always will fit in a size_t */
infof(data,
"Rewinding stream by : %zu"
- " bytes on url %s (size = %" FORMAT_OFF_T
- ", maxdownload = %" FORMAT_OFF_T
- ", bytecount = %" FORMAT_OFF_T ", nread = %zd)\n",
+ " bytes on url %s (size = %" CURL_FORMAT_CURL_OFF_T
+ ", maxdownload = %" CURL_FORMAT_CURL_OFF_T
+ ", bytecount = %" CURL_FORMAT_CURL_OFF_T ", nread = %zd)\n",
excess, data->state.path,
k->size, k->maxdownload, k->bytecount, nread);
read_rewind(conn, excess);
infof(data,
"Excess found in a non pipelined read:"
" excess = %zu"
- ", size = %" FORMAT_OFF_T
- ", maxdownload = %" FORMAT_OFF_T
- ", bytecount = %" FORMAT_OFF_T "\n",
+ ", size = %" CURL_FORMAT_CURL_OFF_T
+ ", maxdownload = %" CURL_FORMAT_CURL_OFF_T
+ ", bytecount = %" CURL_FORMAT_CURL_OFF_T "\n",
excess, k->size, k->maxdownload, k->bytecount);
}
}
if(!k->ignorebody) {
#ifndef CURL_DISABLE_POP3
- if(conn->handler->protocol&CURLPROTO_POP3)
+ if(conn->handler->protocol&PROTO_FAMILY_POP3)
result = Curl_pop3_write(conn, k->str, nread);
else
#endif /* CURL_DISABLE_POP3 */
/*
* We loop here to do the READ and SEND loop until we run out of
* data to send or until we get EWOULDBLOCK back
+ *
+ * FIXME: above comment is misleading. Currently no looping is
+ * actually done in do-while loop below.
*/
do {
/* HTTP pollution, this should be written nicer to become more
protocol agnostic. */
int fillcount;
+ struct HTTP *http = data->req.protop;
if((k->exp100 == EXP100_SENDING_REQUEST) &&
- (data->state.proto.http->sending == HTTPSEND_BODY)) {
+ (http->sending == HTTPSEND_BODY)) {
/* If this call is to send body data, we must take some action:
We have sent off the full HTTP 1.1 request, and we shall now
go into the Expect: 100 state and await such a header */
*didwhat &= ~KEEP_SEND; /* we didn't write anything actually */
/* set a timeout for the multi interface */
- Curl_expire(data, CURL_TIMEOUT_EXPECT_100);
+ Curl_expire(data, data->set.expect_100_timeout);
break;
}
- if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) {
- if(data->state.proto.http->sending == HTTPSEND_REQUEST)
+ if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) {
+ if(http->sending == HTTPSEND_REQUEST)
/* We're sending the HTTP request headers, not the data.
Remember that so we don't change the line endings. */
sending_http_headers = TRUE;
/* store number of bytes available for upload */
data->req.upload_present = nread;
-#ifndef CURL_DISABLE_SMTP
- if(conn->handler->protocol & CURLPROTO_SMTP) {
- result = Curl_smtp_escape_eob(conn, nread);
- if(result)
- return result;
- }
- else
-#endif /* CURL_DISABLE_SMTP */
-
/* convert LF to CRLF if so asked */
- if((!sending_http_headers) &&
+ if((!sending_http_headers) && (
#ifdef CURL_DO_LINEEND_CONV
- /* always convert if we're FTPing in ASCII mode */
- ((data->set.crlf) || (data->set.prefer_ascii))) {
-#else
- (data->set.crlf)) {
+ /* always convert if we're FTPing in ASCII mode */
+ (data->set.prefer_ascii) ||
#endif
- if(data->state.scratch == NULL)
- data->state.scratch = malloc(2*BUFSIZE);
- if(data->state.scratch == NULL) {
- failf (data, "Failed to alloc scratch buffer!");
- return CURLE_OUT_OF_MEMORY;
+ (data->set.crlf))) {
+ /* Do we need to allocate a scratch buffer? */
+ if(!data->state.scratch) {
+ data->state.scratch = malloc(2 * BUFSIZE);
+ if(!data->state.scratch) {
+ failf(data, "Failed to alloc scratch buffer!");
+
+ return CURLE_OUT_OF_MEMORY;
+ }
}
+
/*
* ASCII/EBCDIC Note: This is presumably a text (not binary)
* transfer so the data should already be in ASCII.
if(!data->set.crlf) {
/* we're here only because FTP is in ASCII mode...
bump infilesize for the LF we just added */
- data->set.infilesize++;
+ data->state.infilesize++;
}
}
else
data->state.scratch[si] = data->req.upload_fromhere[i];
}
+
if(si != nread) {
/* only perform the special operation if we really did replace
anything */
data->req.upload_present = nread;
}
}
+
+#ifndef CURL_DISABLE_SMTP
+ if(conn->handler->protocol & PROTO_FAMILY_SMTP) {
+ result = Curl_smtp_escape_eob(conn, nread);
+ if(result)
+ return result;
+ }
+#endif /* CURL_DISABLE_SMTP */
} /* if 0 == data->req.upload_present */
else {
/* We have a partial buffer left from a previous "round". Use
k->writebytecount += bytes_written;
- if(k->writebytecount == data->set.infilesize) {
+ if(k->writebytecount == data->state.infilesize) {
/* we have sent all data we were supposed to */
k->upload_done = TRUE;
infof(data, "We are completely uploaded and fine\n");
Curl_pgrsSetUploadCounter(data, k->writebytecount);
- } while(0); /* just to break out from! */
+ } WHILE_FALSE; /* just to break out from! */
return CURLE_OK;
}
*/
long ms = Curl_tvdiff(k->now, k->start100);
- if(ms > CURL_TIMEOUT_EXPECT_100) {
+ if(ms >= data->set.expect_100_timeout) {
/* we've waited long enough, continue anyway */
k->exp100 = EXP100_SEND_DATA;
k->keepon |= KEEP_SEND;
if(0 > Curl_timeleft(data, &k->now, FALSE)) {
if(k->size != -1) {
failf(data, "Operation timed out after %ld milliseconds with %"
- FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received",
+ CURL_FORMAT_CURL_OFF_T " out of %"
+ CURL_FORMAT_CURL_OFF_T " bytes received",
Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount,
k->size);
}
else {
failf(data, "Operation timed out after %ld milliseconds with %"
- FORMAT_OFF_T " bytes received",
+ CURL_FORMAT_CURL_OFF_T " bytes received",
Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount);
}
return CURLE_OPERATION_TIMEDOUT;
(k->bytecount != (k->size + data->state.crlf_conversions)) &&
#endif /* CURL_DO_LINEEND_CONV */
!data->req.newurl) {
- failf(data, "transfer closed with %" FORMAT_OFF_T
+ failf(data, "transfer closed with %" CURL_FORMAT_CURL_OFF_T
" bytes remaining to read",
k->size - k->bytecount);
return CURLE_PARTIAL_FILE;
(conn->chunk.state != CHUNK_STOP)) {
/*
* In chunked mode, return an error if the connection is closed prior to
- * the empty (terminiating) chunk is read.
+ * the empty (terminating) chunk is read.
*
* The condition above used to check for
* conn->proto.http->chunk.datasize != 0 which is true after reading
}
/* Now update the "done" boolean we return */
- *done = (bool)(0 == (k->keepon&(KEEP_RECV|KEEP_SEND|
- KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)));
+ *done = (0 == (k->keepon&(KEEP_RECV|KEEP_SEND|
+ KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE;
return CURLE_OK;
}
if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) {
if((conn->sockfd != conn->writesockfd) ||
- !(data->req.keepon & KEEP_RECV)) {
- /* only if they are not the same socket or we didn't have a readable
+ bitmap == GETSOCK_BLANK) {
+ /* only if they are not the same socket and we have a readable
one, we increase index */
- if(data->req.keepon & KEEP_RECV)
+ if(bitmap != GETSOCK_BLANK)
sockindex++; /* increase index if we need two entries */
DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD);
/*
* Determine optimum sleep time based on configured rate, current rate,
* and packet size.
- * Returns value in mili-seconds.
+ * Returns value in milliseconds.
*
* The basic idea is to adjust the desired rate up/down in this method
* based on whether we are running too slow or too fast. Then, calculate
- * how many miliseconds to wait for the next packet to achieve this new
+ * how many milliseconds to wait for the next packet to achieve this new
* rate.
*/
long Curl_sleep_time(curl_off_t rate_bps, curl_off_t cur_rate_bps,
rate_bps += rate_bps >> 6;
}
- /* Determine number of miliseconds to wait until we do
+ /* Determine number of milliseconds to wait until we do
* the next packet at the adjusted rate. We should wait
* longer when using larger packets, for instance.
*/
return (long)rv;
}
-
-/*
- * Transfer()
- *
- * This function is what performs the actual transfer. It is capable of doing
- * both ways simultaneously. The transfer must already have been setup by a
- * call to Curl_setup_transfer().
- *
- * Note that headers are created in a preallocated buffer of a default size.
- * That buffer can be enlarged on demand, but it is never shrunken again.
- *
- */
-
-static CURLcode
-Transfer(struct connectdata *conn)
-{
- CURLcode result;
- struct SessionHandle *data = conn->data;
- struct SingleRequest *k = &data->req;
- bool done=FALSE;
- bool first=TRUE;
- int timeout_ms;
- int buffersize;
- int totmp;
-
- if((conn->sockfd == CURL_SOCKET_BAD) &&
- (conn->writesockfd == CURL_SOCKET_BAD))
- /* nothing to read, nothing to write, we're already OK! */
- return CURLE_OK;
-
- /* we want header and/or body, if neither then don't do this! */
- if(!k->getheader && data->set.opt_no_body)
- return CURLE_OK;
-
- while(!done) {
- curl_socket_t fd_read = conn->sockfd;
- curl_socket_t fd_write = conn->writesockfd;
- int keepon = k->keepon;
- timeout_ms = 1000;
-
- if(conn->waitfor) {
- /* if waitfor is set, get the RECV and SEND bits from that but keep the
- other bits */
- keepon &= ~ (KEEP_RECV|KEEP_SEND);
- keepon |= conn->waitfor & (KEEP_RECV|KEEP_SEND);
- }
-
- /* limit-rate logic: if speed exceeds threshold, then do not include fd in
- select set. The current speed is recalculated in each Curl_readwrite()
- call */
- if((keepon & KEEP_SEND) &&
- (!data->set.max_send_speed ||
- (data->progress.ulspeed < data->set.max_send_speed) )) {
- k->keepon &= ~KEEP_SEND_HOLD;
- }
- else {
- if(data->set.upload && data->set.max_send_speed &&
- (data->progress.ulspeed > data->set.max_send_speed) ) {
- /* calculate upload rate-limitation timeout. */
- buffersize = (int)(data->set.buffer_size ?
- data->set.buffer_size : BUFSIZE);
- totmp = (int)Curl_sleep_time(data->set.max_send_speed,
- data->progress.ulspeed, buffersize);
- if(totmp < timeout_ms)
- timeout_ms = totmp;
- }
- fd_write = CURL_SOCKET_BAD;
- if(keepon & KEEP_SEND)
- k->keepon |= KEEP_SEND_HOLD; /* hold it */
- }
-
- if((keepon & KEEP_RECV) &&
- (!data->set.max_recv_speed ||
- (data->progress.dlspeed < data->set.max_recv_speed)) ) {
- k->keepon &= ~KEEP_RECV_HOLD;
- }
- else {
- if((!data->set.upload) && data->set.max_recv_speed &&
- (data->progress.dlspeed > data->set.max_recv_speed)) {
- /* Calculate download rate-limitation timeout. */
- buffersize = (int)(data->set.buffer_size ?
- data->set.buffer_size : BUFSIZE);
- totmp = (int)Curl_sleep_time(data->set.max_recv_speed,
- data->progress.dlspeed, buffersize);
- if(totmp < timeout_ms)
- timeout_ms = totmp;
- }
- fd_read = CURL_SOCKET_BAD;
- if(keepon & KEEP_RECV)
- k->keepon |= KEEP_RECV_HOLD; /* hold it */
- }
-
- /* pause logic. Don't check descriptors for paused connections */
- if(k->keepon & KEEP_RECV_PAUSE)
- fd_read = CURL_SOCKET_BAD;
- if(k->keepon & KEEP_SEND_PAUSE)
- fd_write = CURL_SOCKET_BAD;
-
- /* The *_HOLD and *_PAUSE logic is necessary since even though there might
- be no traffic during the select interval, we still call
- Curl_readwrite() for the timeout case and if we limit transfer speed we
- must make sure that this function doesn't transfer anything while in
- HOLD status.
-
- The no timeout for the first round is for the protocols for which data
- has already been slurped off the socket and thus waiting for action
- won't work since it'll wait even though there is already data present
- to work with. */
- if(first &&
- ((fd_read != CURL_SOCKET_BAD) || (fd_write != CURL_SOCKET_BAD)))
- /* if this is the first lap and one of the file descriptors is fine
- to work with, skip the timeout */
- timeout_ms = 0;
- else {
- totmp = Curl_timeleft(data, &k->now, FALSE);
- if(totmp < 0)
- return CURLE_OPERATION_TIMEDOUT;
- else if(!totmp)
- totmp = 1000;
-
- if(totmp < timeout_ms)
- timeout_ms = totmp;
- }
-
- switch (Curl_socket_ready(fd_read, fd_write, timeout_ms)) {
- case -1: /* select() error, stop reading */
-#ifdef EINTR
- /* The EINTR is not serious, and it seems you might get this more
- often when using the lib in a multi-threaded environment! */
- if(SOCKERRNO == EINTR)
- continue;
-#endif
- return CURLE_RECV_ERROR; /* indicate a network problem */
- case 0: /* timeout */
- default: /* readable descriptors */
-
- result = Curl_readwrite(conn, &done);
- /* "done" signals to us if the transfer(s) are ready */
- break;
- }
- if(result)
- return result;
-
- first = FALSE; /* not the first lap anymore */
- }
-
- return CURLE_OK;
-}
-
-static CURLcode loadhostpairs(struct SessionHandle *data)
-{
- struct curl_slist *hostp;
- char hostname[256];
- char address[256];
- int port;
-
- for(hostp = data->change.resolve; hostp; hostp = hostp->next ) {
- if(!hostp->data)
- continue;
- if(hostp->data[0] == '-') {
- /* TODO: mark an entry for removal */
- }
- else if(3 == sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
- address)) {
- struct Curl_dns_entry *dns;
- Curl_addrinfo *addr;
-
- addr = Curl_str2addr(address, port);
- if(!addr) {
- infof(data, "Resolve %s found illegal!\n", hostp->data);
- continue;
- }
- infof(data, "Added %s:%d:%s to DNS cache\n",
- hostname, port, address);
-
- if(data->share)
- Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
-
- /* put this host in the cache */
- dns = Curl_cache_addr(data, addr, hostname, port);
-
- if(data->share)
- Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
-
- if(!dns)
- return CURLE_OUT_OF_MEMORY;
- }
- }
- data->change.resolve = NULL; /* dealt with now */
-
- return CURLE_OK;
-}
-
-
/*
* Curl_pretransfer() is called immediately before a transfer starts.
*/
CURLcode Curl_pretransfer(struct SessionHandle *data)
{
- CURLcode res;
+ CURLcode result;
if(!data->change.url) {
/* we can't do anything without URL */
failf(data, "No URL set!");
}
/* Init the SSL session ID cache here. We do it here since we want to do it
- after the *_setopt() calls (that could change the size of the cache) but
+ after the *_setopt() calls (that could specify the size of the cache) but
before any transfer takes place. */
- res = Curl_ssl_initsessions(data, data->set.ssl.numsessions);
- if(res)
- return res;
+ result = Curl_ssl_initsessions(data, data->set.ssl.max_ssl_sessions);
+ if(result)
+ return result;
data->set.followlocation=0; /* reset the location-follow counter */
data->state.this_is_a_follow = FALSE; /* reset this */
data->state.errorbuf = FALSE; /* no error has occurred */
data->state.httpversion = 0; /* don't assume any particular server version */
- data->state.ssl_connect_retry = FALSE;
-
data->state.authproblem = FALSE;
data->state.authhost.want = data->set.httpauth;
data->state.authproxy.want = data->set.proxyauth;
/* If there is a list of host pairs to deal with */
if(data->change.resolve)
- res = loadhostpairs(data);
+ result = Curl_loadhostpairs(data);
- if(!res) {
+ if(!result) {
/* Allow data->set.use_port to set which port to use. This needs to be
* disabled for example when we follow Location: headers to URLs using
* different ports! */
#endif
Curl_initinfo(data); /* reset session-specific information "variables" */
+ Curl_pgrsResetTimesSizes(data);
Curl_pgrsStartNow(data);
if(data->set.timeout)
if(data->set.connecttimeout)
Curl_expire(data, data->set.connecttimeout);
+
+ /* In case the handle is re-used and an authentication method was picked
+ in the session we need to make sure we only use the one(s) we now
+ consider to be fine */
+ data->state.authhost.picked &= data->state.authhost.want;
+ data->state.authproxy.picked &= data->state.authproxy.want;
}
- return res;
+ return result;
}
/*
char prot[16]; /* URL protocol string storage */
char letter; /* used for a silly sscanf */
- return (bool)(2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter));
+ return (2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter)) ? TRUE : FALSE;
}
/*
}
}
else {
- /* We got a new absolute path for this server, cut off from the
- first slash */
- pathsep = strchr(protsep, '/');
- if(pathsep) {
- /* When people use badly formatted URLs, such as
- "http://www.url.com?dir=/home/daniel" we must not use the first
- slash, if there's a ?-letter before it! */
- char *sep = strchr(protsep, '?');
- if(sep && (sep < pathsep))
- pathsep = sep;
- *pathsep=0;
+ /* We got a new absolute path for this server */
+
+ if((relurl[0] == '/') && (relurl[1] == '/')) {
+ /* the new URL starts with //, just keep the protocol part from the
+ original one */
+ *protsep=0;
+ useurl = &relurl[2]; /* we keep the slashes from the original, so we
+ skip the new ones */
}
else {
- /* There was no slash. Now, since we might be operating on a badly
- formatted URL, such as "http://www.url.com?id=2380" which doesn't
- use a slash separator as it is supposed to, we need to check for a
- ?-letter as well! */
- pathsep = strchr(protsep, '?');
- if(pathsep)
+ /* cut off the original URL from the first slash, or deal with URLs
+ without slash */
+ pathsep = strchr(protsep, '/');
+ if(pathsep) {
+ /* When people use badly formatted URLs, such as
+ "http://www.url.com?dir=/home/daniel" we must not use the first
+ slash, if there's a ?-letter before it! */
+ char *sep = strchr(protsep, '?');
+ if(sep && (sep < pathsep))
+ pathsep = sep;
*pathsep=0;
+ }
+ else {
+ /* There was no slash. Now, since we might be operating on a badly
+ formatted URL, such as "http://www.url.com?id=2380" which doesn't
+ use a slash separator as it is supposed to, we need to check for a
+ ?-letter as well! */
+ pathsep = strchr(protsep, '?');
+ if(pathsep)
+ *pathsep=0;
+ }
}
}
urllen = strlen(url_clone);
- newest = malloc( urllen + 1 + /* possible slash */
- newlen + 1 /* zero byte */);
+ newest = malloc(urllen + 1 + /* possible slash */
+ newlen + 1 /* zero byte */);
if(!newest) {
free(url_clone); /* don't leak this */
when we get the next URL. We pick the ->url field, which may or may
not be 100% correct */
- if(data->change.referer_alloc)
- /* If we already have an allocated referer, free this first */
- free(data->change.referer);
+ if(data->change.referer_alloc) {
+ Curl_safefree(data->change.referer);
+ data->change.referer_alloc = FALSE;
+ }
data->change.referer = strdup(data->change.url);
- if(!data->change.referer) {
- data->change.referer_alloc = FALSE;
+ if(!data->change.referer)
return CURLE_OUT_OF_MEMORY;
- }
data->change.referer_alloc = TRUE; /* yes, free this later */
}
}
if(disallowport)
data->state.allow_port = FALSE;
- if(data->change.url_alloc)
- free(data->change.url);
- else
- data->change.url_alloc = TRUE; /* the URL is allocated */
+ if(data->change.url_alloc) {
+ Curl_safefree(data->change.url);
+ data->change.url_alloc = FALSE;
+ }
data->change.url = newurl;
+ data->change.url_alloc = TRUE;
newurl = NULL; /* don't free! */
infof(data, "Issue another request to this URL: '%s'\n", data->change.url);
*/
break;
case 301: /* Moved Permanently */
- /* (quote from RFC2616, section 10.3.2):
+ /* (quote from RFC7231, section 6.4.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.
+ * Note: For historical reasons, a user agent MAY change the request
+ * method from POST to GET for the subsequent request. If this
+ * behavior is undesired, the 307 (Temporary Redirect) status code
+ * can be used instead.
*
* ----
*
- * 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.
+ * Many webservers expect this, 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.
*
- * This behaviour can be overridden with CURLOPT_POSTREDIR.
+ * This behaviour is forbidden by RFC1945 and the obsolete RFC2616, and
+ * can be overridden with CURLOPT_POSTREDIR.
*/
if((data->set.httpreq == HTTPREQ_POST
|| data->set.httpreq == HTTPREQ_POST_FORM)
- && !data->set.post301) {
- infof(data,
- "Violate RFC 2616/10.3.2 and switch from POST to GET\n");
+ && !(data->set.keep_post & CURL_REDIR_POST_301)) {
+ infof(data, "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.
-
- This behaviour can be overriden with CURLOPT_POSTREDIR
- */
+ /* (quote from RFC7231, section 6.4.3)
+ *
+ * Note: For historical reasons, a user agent MAY change the request
+ * method from POST to GET for the subsequent request. If this
+ * behavior is undesired, the 307 (Temporary Redirect) status code
+ * can be used instead.
+ *
+ * ----
+ *
+ * Many webservers expect this, 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.
+ *
+ * This behaviour is forbidden by RFC1945 and the obsolete RFC2616, and
+ * can be overridden with CURLOPT_POSTREDIR.
+ */
if((data->set.httpreq == HTTPREQ_POST
|| data->set.httpreq == HTTPREQ_POST_FORM)
- && !data->set.post302) {
- infof(data,
- "Violate RFC 2616/10.3.3 and switch from POST to GET\n");
+ && !(data->set.keep_post & CURL_REDIR_POST_302)) {
+ infof(data, "Switch from POST to GET\n");
data->set.httpreq = HTTPREQ_GET;
}
break;
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) {
+ /* Disable both types of POSTs, unless the user explicitely
+ asks for POST after POST */
+ if(data->set.httpreq != HTTPREQ_GET
+ && !(data->set.keep_post & CURL_REDIR_POST_303)) {
data->set.httpreq = HTTPREQ_GET; /* enforce GET request */
infof(data, "Disables POST, goes with %s\n",
data->set.opt_no_body?"HEAD":"GET");
break;
}
Curl_pgrsTime(data, TIMER_REDIRECT);
- Curl_pgrsResetTimes(data);
+ Curl_pgrsResetTimesSizes(data);
return CURLE_OK;
#endif /* CURL_DISABLE_HTTP */
}
-static CURLcode
-connect_host(struct SessionHandle *data,
- struct connectdata **conn)
-{
- CURLcode res = CURLE_OK;
-
- bool async;
- bool protocol_done=TRUE; /* will be TRUE always since this is only used
- within the easy interface */
- Curl_pgrsTime(data, TIMER_STARTSINGLE);
- 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_resolver_wait_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, /* dead_connection */ FALSE);
- }
-
- return res;
-}
-
CURLcode
Curl_reconnect_request(struct connectdata **connp)
{
infof(data, "Re-used connection seems dead, get a new one\n");
- conn->bits.close = TRUE; /* enforce close of this connection */
+ connclose(conn, "Reconnect dead connection"); /* enforce close */
result = Curl_done(&conn, result, FALSE); /* we are so done with this */
- /* conn may no longer be a good pointer */
+ /* conn may no longer be a good pointer, clear it to avoid mistakes by
+ parent functions */
+ *connp = NULL;
/*
* According to bug report #1330310. We need to check for CURLE_SEND_ERROR
* (again). Slight Lack of feedback in the report, but I don't think this
* extra check can do much harm.
*/
- if((CURLE_OK == result) || (CURLE_SEND_ERROR == result)) {
+ if(!result || (CURLE_SEND_ERROR == result)) {
bool async;
bool protocol_done = TRUE;
/* Now, redo the connect and get a new connection */
result = Curl_connect(data, connp, &async, &protocol_done);
- if(CURLE_OK == result) {
+ if(!result) {
/* We have connected or sent away a name resolve query fine */
conn = *connp; /* setup conn to again point to something nice */
/* if we're talking upload, we can't do the checks below, unless the protocol
is HTTP as when uploading over HTTP we will still get a response */
if(data->set.upload &&
- !(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)))
+ !(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)))
return CURLE_OK;
- if(/* workaround for broken TLS servers */ data->state.ssl_connect_retry ||
- ((data->req.bytecount +
- data->req.headerbytecount == 0) &&
- conn->bits.reuse &&
- !data->set.opt_no_body &&
- data->set.rtspreq != RTSPREQ_RECEIVE)) {
+ if((data->req.bytecount + data->req.headerbytecount == 0) &&
+ conn->bits.reuse &&
+ !data->set.opt_no_body &&
+ (data->set.rtspreq != RTSPREQ_RECEIVE)) {
/* 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
if(!*url)
return CURLE_OUT_OF_MEMORY;
- conn->bits.close = TRUE; /* close this connection */
+ connclose(conn, "retry"); /* 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
transferred! */
- if(data->state.proto.http->writebytecount)
- Curl_readrewind(conn);
- }
- return CURLE_OK;
-}
-
-static CURLcode Curl_do_perform(struct SessionHandle *data)
-{
- CURLcode res;
- CURLcode res2;
- struct connectdata *conn=NULL;
- char *newurl = NULL; /* possibly a new URL to follow to! */
- followtype follow = FOLLOW_NONE;
-
- 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.
- */
-
- for(;;) {
- res = connect_host(data, &conn); /* primary connection */
-
- if(res == CURLE_OK) {
- bool do_done;
- if(data->set.connect_only) {
- /* keep connection open for application to use the socket */
- conn->bits.close = FALSE;
- res = Curl_done(&conn, CURLE_OK, FALSE);
- break;
- }
- res = Curl_do(&conn, &do_done);
-
- if(res == CURLE_OK) {
- if(conn->data->set.wildcardmatch) {
- if(conn->data->wildcard.state == CURLWC_DONE ||
- conn->data->wildcard.state == CURLWC_SKIP) {
- /* keep connection open for application to use the socket */
- conn->bits.close = FALSE;
- res = Curl_done(&conn, CURLE_OK, FALSE);
- break;
- }
- }
- res = Transfer(conn); /* now fetch that URL please */
- if((res == CURLE_OK) || (res == CURLE_RECV_ERROR)) {
- bool retry = FALSE;
- CURLcode rc = Curl_retry_request(conn, &newurl);
- if(rc)
- res = rc;
- else
- retry = (newurl?TRUE:FALSE);
-
- if(retry) {
- /* we know (newurl != NULL) at this point */
- res = CURLE_OK;
- follow = FOLLOW_RETRY;
- }
- else if(res == CURLE_OK) {
- /*
- * We must duplicate the new URL here as the connection data may
- * be free()ed in the Curl_done() function. We prefer the newurl
- * one since that's used for redirects or just further requests
- * for retries or multi-stage HTTP auth methods etc.
- */
- if(data->req.newurl) {
- follow = FOLLOW_REDIR;
- newurl = strdup(data->req.newurl);
- if(!newurl)
- res = CURLE_OUT_OF_MEMORY;
- }
- else if(data->req.location) {
- follow = FOLLOW_FAKE;
- newurl = strdup(data->req.location);
- if(!newurl)
- res = CURLE_OUT_OF_MEMORY;
- }
- }
-
- /* in the above cases where 'newurl' gets assigned, we have a fresh
- * allocated memory pointed to */
- }
- if(res != CURLE_OK) {
- /* The transfer phase returned error, we mark the connection to get
- * closed to prevent being re-used. This is because we can't
- * possibly know if the connection is in a good shape or not now. */
- conn->bits.close = TRUE;
-
- if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) {
- /* if we failed anywhere, we must clean up the secondary socket if
- it was used */
- Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]);
- conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
- }
- }
-
- /* Always run Curl_done(), even if some of the previous calls
- failed, but return the previous (original) error code */
- res2 = Curl_done(&conn, res, FALSE);
-
- if(CURLE_OK == res)
- res = res2;
- }
- else if(conn)
- /* Curl_do() failed, clean up left-overs in the done-call, but note
- that at some cases the conn pointer is NULL when Curl_do() failed
- and the connection cache is very small so only call Curl_done() if
- conn is still "alive". */
- /* ignore return code since we already have an error to return */
- (void)Curl_done(&conn, res, FALSE);
-
- /*
- * Important: 'conn' cannot be used here, since it may have been closed
- * in 'Curl_done' or other functions.
- */
-
- if((res == CURLE_OK) && follow) {
- res = Curl_follow(data, newurl, follow);
- if(CURLE_OK == res) {
- /* if things went fine, Curl_follow() freed or otherwise took
- responsibility for the newurl pointer */
- newurl = NULL;
- if(follow >= FOLLOW_RETRY) {
- follow = FOLLOW_NONE;
- continue;
- }
- /* else we break out of the loop below */
- }
- }
+ if(conn->handler->protocol&PROTO_FAMILY_HTTP) {
+ struct HTTP *http = data->req.protop;
+ if(http->writebytecount)
+ return Curl_readrewind(conn);
}
- break; /* it only reaches here when this shouldn't loop */
-
- } /* loop if Location: */
-
- if(newurl)
- free(newurl);
-
- 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.
- */
- const char *str = curl_easy_strerror(res);
- if(!str)
- failf(data, "unspecified error %d", (int)res);
- else
- failf(data, "%s", str);
}
-
- /* run post-transfer unconditionally, 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_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;
- if(!data->set.wildcardmatch)
- return Curl_do_perform(data);
-
- /* init main wildcard structures */
- res = Curl_wildcard_init(&data->wildcard);
- if(res)
- return res;
-
- res = Curl_do_perform(data);
- if(res) {
- Curl_wildcard_dtor(&data->wildcard);
- return res;
- }
-
- /* wildcard loop */
- while(!res && data->wildcard.state != CURLWC_DONE)
- res = Curl_do_perform(data);
-
- Curl_wildcard_dtor(&data->wildcard);
-
- /* wildcard download finished or failed */
- data->wildcard.state = CURLWC_INIT;
- return res;
+ return CURLE_OK;
}
/*
k->keepon |= KEEP_RECV;
if(conn->writesockfd != CURL_SOCKET_BAD) {
+ struct HTTP *http = data->req.protop;
/* HTTP 1.1 magic:
Even if we require a 100-return code before uploading data, we might
state info where we wait for the 100-return code
*/
if((data->state.expect100header) &&
- (data->state.proto.http->sending == HTTPSEND_BODY)) {
+ (conn->handler->protocol&PROTO_FAMILY_HTTP) &&
+ (http->sending == HTTPSEND_BODY)) {
/* wait with write until we either got 100-continue or a timeout */
k->exp100 = EXP100_AWAITING_CONTINUE;
- k->start100 = k->start;
+ k->start100 = Curl_tvnow();
- /* set a timeout for the multi interface */
- Curl_expire(data, CURL_TIMEOUT_EXPECT_100);
+ /* Set a timeout for the multi interface. Add the inaccuracy margin so
+ that we don't fire slightly too early and get denied to run. */
+ Curl_expire(data, data->set.expect_100_timeout);
}
else {
if(data->state.expect100header)