#include "setup.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <errno.h>
#include "strtoofft.h"
#include "strequal.h"
#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#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
#include <sys/time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#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_SOCKET
#error "We can't compile without socket() support!"
#endif
#endif
#include "urldata.h"
#include <curl/curl.h>
#include "netrc.h"
#include "content_encoding.h"
#include "hostip.h"
#include "transfer.h"
#include "sendf.h"
#include "speedcheck.h"
#include "progress.h"
#include "http.h"
#include "url.h"
#include "getinfo.h"
#include "ssluse.h"
#include "http_digest.h"
#include "http_ntlm.h"
#include "http_negotiate.h"
#include "share.h"
#include "memory.h"
#include "select.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#include "memdebug.h"
#define CURL_TIMEOUT_EXPECT_100 1000
enum {
KEEP_NONE,
KEEP_READ,
KEEP_WRITE
};
CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp)
{
struct SessionHandle *data = conn->data;
size_t buffersize = (size_t)bytes;
int nread;
if(conn->bits.upload_chunky) {
buffersize -= (8 + 2 + 2);
conn->upload_fromhere += 10;
}
nread = (int)conn->fread(conn->upload_fromhere, 1,
buffersize, conn->fread_in);
if(nread == CURL_READFUNC_ABORT) {
failf(data, "operation aborted by callback\n");
return CURLE_ABORTED_BY_CALLBACK;
}
if(!conn->bits.forbidchunk && conn->bits.upload_chunky) {
char hexbuffer[11];
int hexlen = snprintf(hexbuffer, sizeof(hexbuffer),
"%x\r\n", nread);
conn->upload_fromhere -= hexlen;
nread += hexlen;
memcpy(conn->upload_fromhere, hexbuffer, hexlen);
memcpy(conn->upload_fromhere + nread, "\r\n", 2);
if((nread - hexlen) == 0) {
conn->keep.upload_done = TRUE;
}
nread+=2;
}
*nreadp = nread;
return CURLE_OK;
}
static bool
checkhttpprefix(struct SessionHandle *data,
const char *s)
{
struct curl_slist *head = data->set.http200aliases;
while (head) {
if (checkprefix(head->data, s))
return TRUE;
head = head->next;
}
if(checkprefix("HTTP/", s))
return TRUE;
return FALSE;
}
CURLcode Curl_readrewind(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
conn->bits.rewindaftersend = FALSE;
if(data->set.postfields ||
(data->set.httpreq == HTTPREQ_POST_FORM))
;
else {
if(data->set.ioctl) {
curlioerr err;
err = data->set.ioctl(data, CURLIOCMD_RESTARTREAD,
data->set.ioctl_client);
infof(data, "the ioctl callback returned %d\n", (int)err);
if(err) {
failf(data, "ioctl callback returned error %d\n", (int)err);
return CURLE_SEND_FAIL_REWIND;
}
}
else {
if(data->set.fread == (curl_read_callback)fread) {
if(-1 != fseek(data->set.in, 0, SEEK_SET))
return CURLE_OK;
}
failf(data, "necessary data rewind wasn't possible\n");
return CURLE_SEND_FAIL_REWIND;
}
}
return CURLE_OK;
}
#ifdef USE_SSLEAY
static int data_pending(struct connectdata *conn)
{
if(conn->ssl[FIRSTSOCKET].handle)
return SSL_pending(conn->ssl[FIRSTSOCKET].handle);
return 0;
}
#else
#define data_pending(x) 0
#endif
CURLcode Curl_readwrite(struct connectdata *conn,
bool *done)
{
struct Curl_transfer_keeper *k = &conn->keep;
struct SessionHandle *data = conn->data;
CURLcode result;
ssize_t nread;
int didwhat=0;
int fd_read;
int fd_write;
int select_res;
curl_off_t contentlength;
if(k->keepon & KEEP_READ)
fd_read = conn->sockfd;
else
fd_read = CURL_SOCKET_BAD;
if(k->keepon & KEEP_WRITE)
fd_write = conn->writesockfd;
else
fd_write = CURL_SOCKET_BAD;
select_res = Curl_select(fd_read, fd_write, 0);
do {
if((k->keepon & KEEP_READ) && (select_res & CSELECT_IN)) {
bool is_empty_data = FALSE;
do {
size_t buffersize = data->set.buffer_size?
data->set.buffer_size:BUFSIZE;
int readrc = Curl_read(conn, conn->sockfd, k->buf, buffersize, &nread);
if(0>readrc)
break;
result = (CURLcode)readrc;
if(result>0)
return result;
if ((k->bytecount == 0) && (k->writebytecount == 0)) {
Curl_pgrsTime(data, TIMER_STARTTRANSFER);
if(k->wait100_after_headers)
k->start100 = Curl_tvnow();
}
didwhat |= KEEP_READ;
is_empty_data = (nread == 0 && k->bodywrites == 0);
if (0 < nread || is_empty_data)
k->buf[nread] = 0;
else if (0 >= nread) {
k->keepon &= ~KEEP_READ;
break;
}
k->str = k->buf;
if (k->header) {
bool stop_reading = FALSE;
do {
size_t hbufp_index;
size_t rest_length;
size_t full_length;
int writetype;
k->str_start = k->str;
k->end_ptr = strchr (k->str_start, '\n');
if (!k->end_ptr) {
if (k->hbuflen + nread >= data->state.headersize) {
char *newbuff;
size_t newsize=CURLMAX((k->hbuflen+nread)*3/2,
data->state.headersize*2);
hbufp_index = k->hbufp - data->state.headerbuff;
newbuff = (char *)realloc(data->state.headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return CURLE_OUT_OF_MEMORY;
}
data->state.headersize=newsize;
data->state.headerbuff = newbuff;
k->hbufp = data->state.headerbuff + hbufp_index;
}
memcpy(k->hbufp, k->str, nread);
k->hbufp += nread;
k->hbuflen += nread;
if (!k->headerline && (k->hbuflen>5)) {
if(!checkhttpprefix(data, data->state.headerbuff)) {
k->header = FALSE;
k->badheader = HEADER_ALLBAD;
break;
}
}
break;
}
rest_length = (k->end_ptr - k->str)+1;
nread -= rest_length;
k->str = k->end_ptr + 1;
full_length = k->str - k->str_start;
if (k->hbuflen + full_length >=
data->state.headersize) {
char *newbuff;
size_t newsize=CURLMAX((k->hbuflen+full_length)*3/2,
data->state.headersize*2);
hbufp_index = k->hbufp - data->state.headerbuff;
newbuff = (char *)realloc(data->state.headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return CURLE_OUT_OF_MEMORY;
}
data->state.headersize= newsize;
data->state.headerbuff = newbuff;
k->hbufp = data->state.headerbuff + hbufp_index;
}
strncpy (k->hbufp, k->str_start, full_length);
k->hbufp += full_length;
k->hbuflen += full_length;
*k->hbufp = 0;
k->end_ptr = k->hbufp;
k->p = data->state.headerbuff;
if(!k->headerline) {
if((k->hbuflen>5) &&
!checkhttpprefix(data, data->state.headerbuff)) {
k->header = FALSE;
if(nread)
k->badheader = HEADER_PARTHEADER;
else {
k->badheader = HEADER_ALLBAD;
nread = (ssize_t)rest_length;
}
break;
}
}
if (('\n' == *k->p) || ('\r' == *k->p)) {
size_t headerlen;
if ('\r' == *k->p)
k->p++;
if ('\n' == *k->p)
k->p++;
if(100 == k->httpcode) {
k->header = TRUE;
k->headerline = 0;
if (k->write_after_100_header) {
k->write_after_100_header = FALSE;
k->keepon |= KEEP_WRITE;
}
}
else
k->header = FALSE;
if (417 == k->httpcode) {
k->write_after_100_header = FALSE;
k->keepon &= ~KEEP_WRITE;
}
#ifndef CURL_DISABLE_HTTP
if (Curl_http_should_fail(conn)) {
failf (data, "The requested URL returned error: %d",
k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
#endif
writetype = CLIENTWRITE_HEADER;
if (data->set.include_header)
writetype |= CLIENTWRITE_BODY;
headerlen = k->p - data->state.headerbuff;
result = Curl_client_write(data, writetype,
data->state.headerbuff,
headerlen);
if(result)
return result;
data->info.header_size += headerlen;
conn->headerbytecount += headerlen;
conn->deductheadercount =
(100 == k->httpcode)?conn->headerbytecount:0;
if (conn->resume_from &&
(data->set.httpreq==HTTPREQ_GET) &&
(k->httpcode == 416)) {
stop_reading = TRUE;
}
#ifndef CURL_DISABLE_HTTP
if(!stop_reading) {
result = Curl_http_auth_act(conn);
if(result)
return result;
if(conn->bits.rewindaftersend) {
infof(data, "Keep sending data to get tossed away!\n");
k->keepon |= KEEP_WRITE;
}
}
#endif
if(!k->header) {
if(conn->bits.no_body)
stop_reading = TRUE;
else {
if(conn->bits.chunk)
conn->size=-1;
}
if(-1 != conn->size) {
Curl_pgrsSetDownloadSize(data, conn->size);
conn->maxdownload = conn->size;
}
if(0 == conn->maxdownload)
stop_reading = TRUE;
if(stop_reading) {
k->keepon &= ~KEEP_READ;
}
break;
}
k->hbufp = data->state.headerbuff;
k->hbuflen = 0;
continue;
}
if (!k->headerline++) {
int httpversion_major;
int nc=sscanf(k->p, " HTTP/%d.%d %3d",
&httpversion_major,
&k->httpversion,
&k->httpcode);
if (nc==3) {
k->httpversion += 10 * httpversion_major;
}
else {
nc=sscanf(k->p, " HTTP %3d", &k->httpcode);
k->httpversion = 10;
if (!nc) {
if (checkhttpprefix(data, k->p)) {
nc = 1;
k->httpcode = 200;
k->httpversion =
(data->set.httpversion==CURL_HTTP_VERSION_1_0)? 10 : 11;
}
}
}
if (nc) {
data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion;
if (data->set.http_fail_on_error &&
(k->httpcode >= 400) &&
(k->httpcode != 401) &&
(k->httpcode != 407)) {
if (conn->resume_from &&
(data->set.httpreq==HTTPREQ_GET) &&
(k->httpcode == 416)) {
}
else {
failf (data, "The requested URL returned error: %d",
k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
}
if(k->httpversion == 10)
conn->bits.close = TRUE;
switch(k->httpcode) {
case 204:
case 416:
case 304:
conn->size=0;
conn->maxdownload=0;
break;
default:
break;
}
}
else {
k->header = FALSE;
break;
}
}
if ((k->httpcode != 416) &&
checkprefix("Content-Length:", k->p)) {
contentlength = curlx_strtoofft(k->p+15, NULL, 10);
if (data->set.max_filesize &&
contentlength > data->set.max_filesize) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
}
if(contentlength >= 0)
conn->size = contentlength;
else {
conn->bits.close = TRUE;
infof(data, "Negative content-length: %" FORMAT_OFF_T
", closing after transfer\n", contentlength);
}
}
else if (checkprefix("Content-Type:", k->p)) {
char *start;
char *end;
size_t len;
for(start=k->p+13;
*start && isspace((int)*start);
start++)
;
end = strchr(start, '\r');
if(!end)
end = strchr(start, '\n');
if(end) {
for(; isspace((int)*end) && (end > start); end--)
;
len = end-start+1;
Curl_safefree(data->info.contenttype);
data->info.contenttype = malloc(len + 1);
if (NULL == data->info.contenttype)
return CURLE_OUT_OF_MEMORY;
memcpy(data->info.contenttype, start, len);
data->info.contenttype[len] = 0;
}
}
#ifndef CURL_DISABLE_HTTP
else if((k->httpversion == 10) &&
conn->bits.httpproxy &&
Curl_compareheader(k->p,
"Proxy-Connection:", "keep-alive")) {
conn->bits.close = FALSE;
infof(data, "HTTP/1.0 proxy connection set to keep alive!\n");
}
else if((k->httpversion == 11) &&
conn->bits.httpproxy &&
Curl_compareheader(k->p,
"Proxy-Connection:", "close")) {
conn->bits.close = TRUE;
infof(data, "HTTP/1.1 proxy connection set close!\n");
}
else if((k->httpversion == 10) &&
Curl_compareheader(k->p, "Connection:", "keep-alive")) {
conn->bits.close = FALSE;
infof(data, "HTTP/1.0 connection set to keep alive!\n");
}
else if (Curl_compareheader(k->p, "Connection:", "close")) {
conn->bits.close = TRUE;
}
else if (Curl_compareheader(k->p,
"Transfer-Encoding:", "chunked")) {
conn->bits.chunk = TRUE;
Curl_httpchunk_init(conn);
}
else if (checkprefix("Content-Encoding:", k->p) &&
data->set.encoding) {
char *start;
for(start=k->p+17;
*start && isspace((int)*start);
start++)
;
if (checkprefix("identity", start))
k->content_encoding = IDENTITY;
else if (checkprefix("deflate", start))
k->content_encoding = DEFLATE;
else if (checkprefix("gzip", start)
|| checkprefix("x-gzip", start))
k->content_encoding = GZIP;
else if (checkprefix("compress", start)
|| checkprefix("x-compress", start))
k->content_encoding = COMPRESS;
}
else if (Curl_compareheader(k->p, "Content-Range:", "bytes")) {
char *ptr = strstr(k->p, "bytes");
ptr+=5;
if(*ptr == ':')
ptr++;
k->offset = curlx_strtoofft(ptr, NULL, 10);
if (conn->resume_from == k->offset)
k->content_range = TRUE;
}
#if !defined(CURL_DISABLE_COOKIES)
else if(data->cookies &&
checkprefix("Set-Cookie:", k->p)) {
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE,
CURL_LOCK_ACCESS_SINGLE);
Curl_cookie_add(data,
data->cookies, TRUE, k->p+11,
conn->allocptr.cookiehost?
conn->allocptr.cookiehost:conn->host.name,
conn->path);
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}
#endif
else if(checkprefix("Last-Modified:", k->p) &&
(data->set.timecondition || data->set.get_filetime) ) {
time_t secs=time(NULL);
k->timeofdoc = curl_getdate(k->p+strlen("Last-Modified:"),
&secs);
if(data->set.get_filetime)
data->info.filetime = k->timeofdoc;
}
else if((checkprefix("WWW-Authenticate:", k->p) &&
(401 == k->httpcode)) ||
(checkprefix("Proxy-authenticate:", k->p) &&
(407 == k->httpcode))) {
result = Curl_http_input_auth(conn, k->httpcode, k->p);
if(result)
return result;
}
else if ((k->httpcode >= 300 && k->httpcode < 400) &&
checkprefix("Location:", k->p)) {
if(data->set.http_follow_location) {
char *ptr;
char *start=k->p;
char backup;
start += 9;
while(*start && isspace((int)*start ))
start++;
ptr = k->end_ptr-1;
while((ptr>=start) && isspace((int)*ptr))
ptr--;
ptr++;
backup = *ptr;
if(ptr != start) {
*ptr = '\0';
conn->newurl = strdup(start);
*ptr = backup;
if(!conn->newurl)
return CURLE_OUT_OF_MEMORY;
}
}
}
#endif
writetype = CLIENTWRITE_HEADER;
if (data->set.include_header)
writetype |= CLIENTWRITE_BODY;
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
k->p, k->hbuflen, conn);
result = Curl_client_write(data, writetype, k->p, k->hbuflen);
if(result)
return result;
data->info.header_size += k->hbuflen;
conn->headerbytecount += k->hbuflen;
k->hbufp = data->state.headerbuff;
k->hbuflen = 0;
}
while (!stop_reading && *k->str);
if(stop_reading)
break;
}
if (k->str && !k->header && (nread > 0 || is_empty_data)) {
if(0 == k->bodywrites && !is_empty_data) {
if(conn->protocol&PROT_HTTP) {
if (conn->newurl) {
if(conn->bits.close) {
k->keepon &= ~KEEP_READ;
*done = TRUE;
return CURLE_OK;
}
k->ignorebody = TRUE;
infof(data, "Ignoring the response-body\n");
}
if (conn->resume_from && !k->content_range &&
(data->set.httpreq==HTTPREQ_GET) &&
!k->ignorebody) {
failf(data, "HTTP server doesn't seem to support "
"byte ranges. Cannot resume.");
return CURLE_HTTP_RANGE_ERROR;
}
if(data->set.timecondition && !conn->range) {
if((k->timeofdoc > 0) && (data->set.timevalue > 0)) {
switch(data->set.timecondition) {
case CURL_TIMECOND_IFMODSINCE:
default:
if(k->timeofdoc < data->set.timevalue) {
infof(data,
"The requested document is not new enough\n");
*done = TRUE;
return CURLE_OK;
}
break;
case CURL_TIMECOND_IFUNMODSINCE:
if(k->timeofdoc > data->set.timevalue) {
infof(data,
"The requested document is not old enough\n");
*done = TRUE;
return CURLE_OK;
}
break;
}
}
}
}
}
k->bodywrites++;
if(data->set.verbose) {
if(k->badheader) {
Curl_debug(data, CURLINFO_DATA_IN, data->state.headerbuff,
k->hbuflen, conn);
if(k->badheader == HEADER_PARTHEADER)
Curl_debug(data, CURLINFO_DATA_IN, k->str, nread, conn);
}
else
Curl_debug(data, CURLINFO_DATA_IN, k->str, nread, conn);
}
#ifndef CURL_DISABLE_HTTP
if(conn->bits.chunk) {
CHUNKcode res =
Curl_httpchunk_read(conn, k->str, nread, &nread);
if(CHUNKE_OK < res) {
if(CHUNKE_WRITE_ERROR == res) {
failf(data, "Failed writing data");
return CURLE_WRITE_ERROR;
}
failf(data, "Received problem %d in the chunky parser", res);
return CURLE_RECV_ERROR;
}
else if(CHUNKE_STOP == res) {
k->keepon &= ~KEEP_READ;
}
}
#endif
if((-1 != conn->maxdownload) &&
(k->bytecount + nread >= conn->maxdownload)) {
nread = (ssize_t) (conn->maxdownload - k->bytecount);
if(nread < 0 )
nread = 0;
k->keepon &= ~KEEP_READ;
}
k->bytecount += nread;
Curl_pgrsSetDownloadCounter(data, k->bytecount);
if(!conn->bits.chunk && (nread || k->badheader || is_empty_data)) {
if(k->badheader && !k->ignorebody) {
result = Curl_client_write(data, CLIENTWRITE_BODY,
data->state.headerbuff,
k->hbuflen);
}
if(k->badheader < HEADER_ALLBAD) {
#ifdef HAVE_LIBZ
switch (k->content_encoding) {
case IDENTITY:
#endif
if(!k->ignorebody)
result = Curl_client_write(data, CLIENTWRITE_BODY, k->str,
nread);
#ifdef HAVE_LIBZ
break;
case DEFLATE:
result = Curl_unencode_deflate_write(data, k, nread);
break;
case GZIP:
result = Curl_unencode_gzip_write(data, k, nread);
break;
case COMPRESS:
default:
failf (data, "Unrecognized content encoding type. "
"libcurl understands `identity', `deflate' and `gzip' "
"content encodings.");
result = CURLE_BAD_CONTENT_ENCODING;
break;
}
#endif
}
k->badheader = HEADER_NORMAL;
if(result)
return result;
}
}
if (is_empty_data) {
k->keepon &= ~KEEP_READ;
}
} while(data_pending(conn));
}
if((k->keepon & KEEP_WRITE) && (select_res & CSELECT_OUT)) {
int i, si;
ssize_t bytes_written;
bool writedone=TRUE;
if ((k->bytecount == 0) && (k->writebytecount == 0))
Curl_pgrsTime(data, TIMER_STARTTRANSFER);
didwhat |= KEEP_WRITE;
do {
if(0 == conn->upload_present) {
conn->upload_fromhere = k->uploadbuf;
if(!k->upload_done) {
int fillcount;
if(k->wait100_after_headers &&
(conn->proto.http->sending == HTTPSEND_BODY)) {
k->wait100_after_headers = FALSE;
k->write_after_100_header = TRUE;
k->keepon &= ~KEEP_WRITE;
k->start100 = Curl_tvnow();
didwhat &= ~KEEP_WRITE;
break;
}
result = Curl_fillreadbuffer(conn, BUFSIZE, &fillcount);
if(result)
return result;
nread = (ssize_t)fillcount;
}
else
nread = 0;
if (nread<=0) {
k->keepon &= ~KEEP_WRITE;
writedone = TRUE;
if(conn->bits.rewindaftersend) {
result = Curl_readrewind(conn);
if(result)
return result;
}
break;
}
conn->upload_present = nread;
if (data->set.crlf) {
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;
}
for(i = 0, si = 0; i < nread; i++, si++) {
if (conn->upload_fromhere[i] == 0x0a) {
data->state.scratch[si++] = 0x0d;
data->state.scratch[si] = 0x0a;
}
else
data->state.scratch[si] = conn->upload_fromhere[i];
}
if(si != nread) {
nread = si;
conn->upload_fromhere = data->state.scratch;
conn->upload_present = nread;
}
}
}
else {
}
result = Curl_write(conn,
conn->writesockfd,
conn->upload_fromhere,
conn->upload_present,
&bytes_written);
if(result)
return result;
if(data->set.verbose)
Curl_debug(data, CURLINFO_DATA_OUT, conn->upload_fromhere,
bytes_written, conn);
if(conn->upload_present != bytes_written) {
conn->upload_present -= bytes_written;
conn->upload_fromhere += bytes_written;
writedone = TRUE;
}
else {
conn->upload_fromhere = k->uploadbuf;
conn->upload_present = 0;
if(k->upload_done) {
k->keepon &= ~KEEP_WRITE;
writedone = TRUE;
}
}
k->writebytecount += bytes_written;
Curl_pgrsSetUploadCounter(data, k->writebytecount);
} while(!writedone);
}
} while(0);
k->now = Curl_tvnow();
if(didwhat) {
if(conn->bytecountp)
*conn->bytecountp = k->bytecount;
if(conn->writebytecountp)
*conn->writebytecountp = k->writebytecount;
}
else {
if (k->write_after_100_header) {
long ms = Curl_tvdiff(k->now, k->start100);
if(ms > CURL_TIMEOUT_EXPECT_100) {
k->write_after_100_header = FALSE;
k->keepon |= KEEP_WRITE;
}
}
}
if(Curl_pgrsUpdate(conn))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, k->now);
if (result)
return result;
if (data->set.timeout &&
((Curl_tvdiff(k->now, k->start)/1000) >= data->set.timeout)) {
failf(data, "Operation timed out with %" FORMAT_OFF_T
" out of %" FORMAT_OFF_T " bytes received",
k->bytecount, conn->size);
return CURLE_OPERATION_TIMEOUTED;
}
if(!k->keepon) {
if(!(conn->bits.no_body) && (conn->size != -1) &&
(k->bytecount != conn->size) &&
!conn->newurl) {
failf(data, "transfer closed with %" FORMAT_OFF_T
" bytes remaining to read",
conn->size - k->bytecount);
return CURLE_PARTIAL_FILE;
}
else if(conn->bits.chunk &&
(conn->proto.http->chunk.state != CHUNK_STOP)) {
failf(data, "transfer closed with outstanding read data remaining");
return CURLE_PARTIAL_FILE;
}
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
}
*done = !k->keepon;
return CURLE_OK;
}
CURLcode Curl_readwrite_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct Curl_transfer_keeper *k = &conn->keep;
memset(k, 0, sizeof(struct Curl_transfer_keeper));
k->start = Curl_tvnow();
k->now = k->start;
k->header = TRUE;
k->httpversion = -1;
data = conn->data;
k->buf = data->state.buffer;
k->uploadbuf = data->state.uploadbuffer;
k->maxfd = (conn->sockfd>conn->writesockfd?
conn->sockfd:conn->writesockfd)+1;
k->hbufp = data->state.headerbuff;
k->ignorebody=FALSE;
Curl_pgrsTime(data, TIMER_PRETRANSFER);
Curl_speedinit(data);
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
if (!conn->bits.getheader) {
k->header = FALSE;
if(conn->size > 0)
Curl_pgrsSetDownloadSize(data, conn->size);
}
if(conn->bits.getheader || !conn->bits.no_body) {
if(conn->sockfd != CURL_SOCKET_BAD) {
k->keepon |= KEEP_READ;
}
if(conn->writesockfd != CURL_SOCKET_BAD) {
if (data->set.expect100header &&
(conn->proto.http->sending == HTTPSEND_BODY)) {
k->write_after_100_header = TRUE;
k->start100 = k->start;
}
else {
if(data->set.expect100header)
k->wait100_after_headers = TRUE;
k->keepon |= KEEP_WRITE;
}
}
}
return CURLE_OK;
}
void Curl_single_fdset(struct connectdata *conn,
fd_set *read_fd_set,
fd_set *write_fd_set,
fd_set *exc_fd_set,
int *max_fd)
{
*max_fd = -1;
if(conn->keep.keepon & KEEP_READ) {
FD_SET(conn->sockfd, read_fd_set);
*max_fd = conn->sockfd;
}
if(conn->keep.keepon & KEEP_WRITE) {
FD_SET(conn->writesockfd, write_fd_set);
if((int)conn->writesockfd > *max_fd)
*max_fd = conn->writesockfd;
}
*exc_fd_set = *exc_fd_set;
}
static CURLcode
Transfer(struct connectdata *conn)
{
CURLcode result;
struct Curl_transfer_keeper *k = &conn->keep;
bool done=FALSE;
if(!(conn->protocol & PROT_FILE))
Curl_readwrite_init(conn);
if((conn->sockfd == CURL_SOCKET_BAD) &&
(conn->writesockfd == CURL_SOCKET_BAD))
return CURLE_OK;
if(!conn->bits.getheader && conn->bits.no_body)
return CURLE_OK;
while (!done) {
int fd_read;
int fd_write;
int interval_ms;
interval_ms = 1 * 1000;
if(k->keepon & KEEP_READ)
fd_read = conn->sockfd;
else
fd_read = CURL_SOCKET_BAD;
if(k->keepon & KEEP_WRITE)
fd_write = conn->writesockfd;
else
fd_write = CURL_SOCKET_BAD;
switch (Curl_select(fd_read, fd_write, interval_ms)) {
case -1:
#ifdef EINTR
if(errno == EINTR)
;
else
#endif
done = TRUE;
continue;
case 0:
default:
result = Curl_readwrite(conn, &done);
break;
}
if(result)
return result;
}
return CURLE_OK;
}
CURLcode Curl_pretransfer(struct SessionHandle *data)
{
if(!data->change.url)
return CURLE_URL_MALFORMAT;
#ifdef USE_SSLEAY
{
CURLcode res = Curl_SSL_InitSessions(data, data->set.ssl.numsessions);
if(res)
return res;
}
#endif
data->set.followlocation=0;
data->state.this_is_a_follow = FALSE;
data->state.errorbuf = FALSE;
data->state.authproblem = FALSE;
data->state.authhost.want = data->set.httpauth;
data->state.authproxy.want = data->set.proxyauth;
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
if(data->change.cookielist) {
struct curl_slist *list = data->change.cookielist;
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
while(list) {
data->cookies = Curl_cookie_init(data,
list->data,
data->cookies,
data->set.cookiesession);
list = list->next;
}
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
curl_slist_free_all(data->change.cookielist);
data->change.cookielist = NULL;
}
#endif
data->state.allow_port = TRUE;
#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL)
if(!data->set.no_signal)
data->state.prev_signal = signal(SIGPIPE, SIG_IGN);
#endif
Curl_initinfo(data);
Curl_pgrsStartNow(data);
return CURLE_OK;
}
CURLcode Curl_posttransfer(struct SessionHandle *data)
{
#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL)
if(!data->set.no_signal)
signal(SIGPIPE, data->state.prev_signal);
#else
(void)data;
#endif
if(!(data->progress.flags & PGRS_HIDE) &&
!data->progress.callback)
fprintf(data->set.err, "\n");
return CURLE_OK;
}
static int strlen_url(char *url)
{
char *ptr;
int newlen=0;
bool left=TRUE;
for(ptr=url; *ptr; ptr++) {
switch(*ptr) {
case '?':
left=FALSE;
default:
newlen++;
break;
case ' ':
if(left)
newlen+=3;
else
newlen++;
break;
}
}
return newlen;
}
static void strcpy_url(char *output, char *url)
{
bool left=TRUE;
char *iptr;
char *optr = output;
for(iptr = url;
*iptr;
iptr++) {
switch(*iptr) {
case '?':
left=FALSE;
default:
*optr++=*iptr;
break;
case ' ':
if(left) {
*optr++='%';
*optr++='2';
*optr++='0';
}
else
*optr++='+';
break;
}
}
*optr=0;
}
CURLcode Curl_follow(struct SessionHandle *data,
char *newurl,
bool retry)
{
char prot[16];
char letter;
size_t newlen;
char *newest;
if (data->set.maxredirs &&
(data->set.followlocation >= data->set.maxredirs)) {
failf(data,"Maximum (%d) redirects followed", data->set.maxredirs);
return CURLE_TOO_MANY_REDIRECTS;
}
if(!retry)
data->state.this_is_a_follow = TRUE;
data->set.followlocation++;
if(data->set.http_auto_referer) {
if(data->change.referer_alloc)
free(data->change.referer);
data->change.referer = strdup(data->change.url);
data->change.referer_alloc = TRUE;
}
if(2 != sscanf(newurl, "%15[^?&/:]://%c", prot, &letter)) {
char *protsep;
char *pathsep;
char *useurl = newurl;
size_t urllen;
char *url_clone=strdup(data->change.url);
if(!url_clone)
return CURLE_OUT_OF_MEMORY;
protsep=strstr(url_clone, "//");
if(!protsep)
protsep=url_clone;
else
protsep+=2;
if('/' != newurl[0]) {
int level=0;
pathsep = strrchr(protsep, '?');
if(pathsep)
*pathsep=0;
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep=0;
pathsep = strchr(protsep, '/');
if(pathsep)
protsep = pathsep+1;
else
protsep = NULL;
if((useurl[0] == '.') && (useurl[1] == '/'))
useurl+=2;
while((useurl[0] == '.') &&
(useurl[1] == '.') &&
(useurl[2] == '/')) {
level++;
useurl+=3;
}
if(protsep) {
while(level--) {
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep=0;
else {
*protsep=0;
break;
}
}
}
}
else {
pathsep = strchr(protsep, '/');
if(pathsep) {
char *sep = strchr(protsep, '?');
if(sep && (sep < pathsep))
pathsep = sep;
*pathsep=0;
}
else {
pathsep = strchr(protsep, '?');
if(pathsep)
*pathsep=0;
}
}
newlen = strlen_url(useurl);
urllen = strlen(url_clone);
newest=(char *)malloc( urllen + 1 +
newlen + 1 );
if(!newest) {
free(url_clone);
return CURLE_OUT_OF_MEMORY;
}
memcpy(newest, url_clone, urllen);
if(('/' == useurl[0]) || (protsep && !*protsep))
;
else
newest[urllen++]='/';
strcpy_url(&newest[urllen], useurl);
free(newurl);
free(url_clone);
newurl = newest;
}
else {
data->state.allow_port = FALSE;
if(strchr(newurl, ' ')) {
newlen = strlen_url(newurl);
newest = malloc(newlen+1);
if(newest) {
strcpy_url(newest, newurl);
free(newurl);
newurl = newest;
}
}
}
if(data->change.url_alloc)
free(data->change.url);
else
data->change.url_alloc = TRUE;
data->change.url = newurl;
newurl = NULL;
infof(data, "Issue another request to this URL: '%s'\n", data->change.url);
switch(data->info.httpcode) {
default:
break;
case 301:
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:
case 303:
if(data->set.httpreq != HTTPREQ_GET) {
data->set.httpreq = HTTPREQ_GET;
infof(data, "Disables POST, goes with %s\n",
data->set.opt_no_body?"HEAD":"GET");
}
break;
case 304:
break;
case 305:
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;
Curl_pgrsTime(data, TIMER_STARTSINGLE);
data->change.url_changed = FALSE;
res = Curl_connect(data, conn, &async, &protocol_done);
if((CURLE_OK == res) && async) {
res = Curl_wait_for_resolv(*conn, NULL);
if(CURLE_OK == res)
res = Curl_async_resolved(*conn, &protocol_done);
else
(void)Curl_disconnect(*conn);
}
if(res)
break;
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);
res = Curl_follow(data, gotourl, FALSE);
if(res)
free(gotourl);
}
}
} while (urlchanged && res == CURLE_OK);
return res;
}
bool Curl_retry_request(struct connectdata *conn,
char **url)
{
bool retry = FALSE;
if((conn->keep.bytecount+conn->headerbytecount == 0) &&
conn->bits.reuse) {
infof(conn->data, "Connection died, retrying a fresh connect\n");
*url = strdup(conn->data->change.url);
conn->bits.close = TRUE;
conn->bits.retry = TRUE;
retry = TRUE;
}
return retry;
}
CURLcode Curl_perform(struct SessionHandle *data)
{
CURLcode res;
CURLcode res2;
struct connectdata *conn=NULL;
char *newurl = NULL;
bool retry = FALSE;
data->state.used_interface = Curl_if_easy;
res = Curl_pretransfer(data);
if(res)
return res;
do {
res = Curl_connect_host(data, &conn);
if(res == CURLE_OK) {
if (data->set.source_url)
res = Curl_second_connect(conn);
else
conn->sec_conn = NULL;
}
if(res == CURLE_OK) {
bool do_done;
res = Curl_do(&conn, &do_done);
if(res == CURLE_OK && !data->set.source_url) {
res = Transfer(conn);
if(res == CURLE_OK) {
retry = Curl_retry_request(conn, &newurl);
if(!retry)
newurl = conn->newurl?strdup(conn->newurl):NULL;
}
else {
conn->bits.close = TRUE;
if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) {
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
}
}
res2 = Curl_done(&conn, res);
if(CURLE_OK == res)
res = res2;
}
else
res2 = Curl_done(&conn, res);
if((res == CURLE_OK) && newurl) {
res = Curl_follow(data, newurl, retry);
if(CURLE_OK == res) {
newurl = NULL;
continue;
}
}
}
break;
} while(1);
if(newurl)
free(newurl);
res2 = Curl_posttransfer(data);
if(!res && res2)
res = res2;
return res;
}
CURLcode
Curl_Transfer(struct connectdata *c_conn,
int sockindex,
curl_off_t size,
bool getheader,
curl_off_t *bytecountp,
int writesockindex,
curl_off_t *writecountp
)
{
struct connectdata *conn = (struct connectdata *)c_conn;
if(!conn)
return CURLE_BAD_FUNCTION_ARGUMENT;
curlassert((sockindex <= 1) && (sockindex >= -1));
conn->sockfd = sockindex==-1?
CURL_SOCKET_BAD:conn->sock[sockindex];
conn->size = size;
conn->bits.getheader = getheader;
conn->bytecountp = bytecountp;
conn->writesockfd = writesockindex==-1?
CURL_SOCKET_BAD:conn->sock[writesockindex];
conn->writebytecountp = writecountp;
return CURLE_OK;
}
CURLcode Curl_second_connect(struct connectdata *conn)
{
CURLcode status = CURLE_OK;
struct SessionHandle *data = conn->data;
struct connectdata *sec_conn = NULL;
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;
#if 0
if (strequal(conn->host.dispname, data->set.source_host))
#endif
data->set.reuse_fresh = TRUE;
data->set.userpwd = data->set.source_userpwd;
status = Curl_connect_host(data, &sec_conn);
if(CURLE_OK == status) {
sec_conn->sec_conn = NULL;
sec_conn->data = data;
conn->sec_conn = sec_conn;
}
data->set.reuse_fresh = backup_reuse_fresh;
data->set.userpwd = backup_userpwd;
return status;
}