#include "setup.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include "strtoofft.h"
#include "strequal.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
#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 "sslgen.h"
#include "http_digest.h"
#include "http_ntlm.h"
#include "http_negotiate.h"
#include "share.h"
#include "memory.h"
#include "select.h"
#include "multiif.h"
#include "easyif.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#include "memdebug.h"
#define CURL_TIMEOUT_EXPECT_100 1000
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);
data->reqdata.upload_fromhere += 10;
}
nread = (int)conn->fread(data->reqdata.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);
data->reqdata.upload_fromhere -= hexlen;
nread += hexlen;
memcpy(data->reqdata.upload_fromhere, hexbuffer, hexlen);
memcpy(data->reqdata.upload_fromhere + nread, "\r\n", 2);
if((nread - hexlen) == 0) {
data->reqdata.keep.upload_done = TRUE;
}
nread+=2;
}
*nreadp = nread;
#ifdef CURL_DOES_CONVERSIONS
if(data->set.prefer_ascii) {
CURLcode res;
res = Curl_convert_to_network(data, data->reqdata.upload_fromhere, nread);
if(res != CURLE_OK) {
return(res);
}
}
#endif
return CURLE_OK;
}
static bool
checkhttpprefix(struct SessionHandle *data,
const char *s)
{
struct curl_slist *head = data->set.http200aliases;
bool rc = FALSE;
#ifdef CURL_DOES_CONVERSIONS
char *scratch = calloc(1, strlen(s)+1);
if (NULL == scratch) {
failf (data, "Failed to calloc memory for conversion!");
return FALSE;
}
strcpy(scratch, s);
if (CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) {
free(scratch);
return FALSE;
}
s = scratch;
#endif
while (head) {
if (checkprefix(head->data, s)) {
rc = TRUE;
break;
}
head = head->next;
}
if ((rc != TRUE) && (checkprefix("HTTP/", s))) {
rc = TRUE;
}
#ifdef CURL_DOES_CONVERSIONS
free(scratch);
#endif
return rc;
}
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;
}
static int data_pending(struct connectdata *conn)
{
return conn->protocol&(PROT_SCP|PROT_SFTP) ||
Curl_ssl_data_pending(conn, FIRSTSOCKET);
}
#ifndef MIN
#define MIN(a,b) (a < b ? a : b)
#endif
static void read_rewind(struct connectdata *conn,
size_t thismuch)
{
conn->read_pos -= thismuch;
conn->bits.stream_was_rewound = TRUE;
#ifdef CURLDEBUG
{
char buf[512 + 1];
size_t show;
show = MIN(conn->buf_len - conn->read_pos, sizeof(buf)-1);
if (conn->master_buffer) {
memcpy(buf, conn->master_buffer + conn->read_pos, show);
buf[show] = '\0';
}
else {
buf[0] = '\0';
}
DEBUGF(infof(conn->data,
"Buffer after stream rewind (read_pos = %d): [%s]",
conn->read_pos, buf));
}
#endif
}
CURLcode Curl_readwrite(struct connectdata *conn,
bool *done)
{
struct SessionHandle *data = conn->data;
struct Curl_transfer_keeper *k = &data->reqdata.keep;
CURLcode result;
ssize_t nread;
int didwhat=0;
curl_socket_t fd_read;
curl_socket_t fd_write;
curl_off_t contentlength;
int select_res = conn->cselect_bits;
conn->cselect_bits = 0;
if((k->keepon & (KEEP_READ|KEEP_READ_HOLD)) == KEEP_READ)
fd_read = conn->sockfd;
else
fd_read = CURL_SOCKET_BAD;
if((k->keepon & (KEEP_WRITE|KEEP_WRITE_HOLD)) == KEEP_WRITE)
fd_write = conn->writesockfd;
else
fd_write = CURL_SOCKET_BAD;
if (!select_res) {
select_res = Curl_socket_ready(fd_read, fd_write, 0);
}
if(select_res == CURL_CSELECT_ERR) {
failf(data, "select/poll returned error");
return CURLE_SEND_ERROR;
}
do {
if((k->keepon & KEEP_READ) &&
((select_res & CURL_CSELECT_IN) || conn->bits.stream_was_rewound)) {
bool is_empty_data = FALSE;
do {
size_t buffersize = data->set.buffer_size?
data->set.buffer_size : BUFSIZE;
size_t bytestoread = buffersize;
int readrc;
if (k->size != -1 && !k->header) {
curl_off_t totalleft = k->size - k->bytecount;
if(totalleft < (curl_off_t)bytestoread)
bytestoread = (size_t)totalleft;
}
readrc = Curl_read(conn, conn->sockfd, k->buf, bytestoread, &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 = (bool)((nread == 0) && (k->bodywrites == 0));
if (0 < nread || is_empty_data) {
k->buf[nread] = 0;
}
else if (0 >= nread) {
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing\n"));
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 = memchr(k->str_start, 0x0a, nread);
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 -= (ssize_t)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;
}
memcpy(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 ((0x0a == *k->p) || (0x0d == *k->p)) {
size_t headerlen;
#ifdef CURL_DOES_CONVERSIONS
if (0x0d == *k->p) {
*k->p = '\r';
k->p++;
}
if (0x0a == *k->p) {
*k->p = '\n';
k->p++;
}
#else
if ('\r' == *k->p)
k->p++;
if ('\n' == *k->p)
k->p++;
#endif
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((k->size == -1) && !conn->bits.chunk && !conn->bits.close &&
(k->httpversion >= 11) )
conn->bits.no_body = TRUE;
}
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(conn, writetype,
data->state.headerbuff,
headerlen);
if(result)
return result;
data->info.header_size += (long)headerlen;
data->reqdata.keep.headerbytecount += (long)headerlen;
data->reqdata.keep.deductheadercount =
(100 == k->httpcode)?data->reqdata.keep.headerbytecount:0;
if (data->reqdata.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)
k->size=-1;
}
if(-1 != k->size) {
Curl_pgrsSetDownloadSize(data, k->size);
k->maxdownload = k->size;
}
if(0 == k->maxdownload)
stop_reading = TRUE;
if(stop_reading) {
k->keepon &= ~KEEP_READ;
}
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
k->str_start, headerlen, conn);
break;
}
k->hbufp = data->state.headerbuff;
k->hbuflen = 0;
continue;
}
if (!k->headerline++) {
int httpversion_major;
int nc;
#ifdef CURL_DOES_CONVERSIONS
#define HEADER1 scratch
#define SCRATCHSIZE 21
CURLcode res;
char scratch[SCRATCHSIZE+1];
strncpy(&scratch[0], k->p, SCRATCHSIZE);
scratch[SCRATCHSIZE] = 0;
res = Curl_convert_from_network(data,
&scratch[0],
SCRATCHSIZE);
if (CURLE_OK != res) {
return res;
}
#else
#define HEADER1 k->p
#endif
nc = sscanf(HEADER1,
" HTTP/%d.%d %3d",
&httpversion_major,
&k->httpversion,
&k->httpcode);
if (nc==3) {
k->httpversion += 10 * httpversion_major;
}
else {
nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode);
k->httpversion = 10;
if (!nc) {
if (checkhttpprefix(data, k->p)) {
nc = 1;
k->httpcode = 200;
k->httpversion = 10;
}
}
}
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) || !conn->bits.user_passwd) &&
((k->httpcode != 407) || !conn->bits.proxy_user_passwd) ) {
if (data->reqdata.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:
k->size=0;
k->maxdownload=0;
k->ignorecl = TRUE;
break;
default:
break;
}
}
else {
k->header = FALSE;
break;
}
}
#ifdef CURL_DOES_CONVERSIONS
result = Curl_convert_from_network(data, k->p, strlen(k->p));
if (CURLE_OK != result) {
return(result);
}
#endif
if (!k->ignorecl && !data->set.ignorecl &&
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) {
k->size = contentlength;
k->maxdownload = k->size;
}
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(*start);
start++)
;
end = strchr(start, '\r');
if(!end)
end = strchr(start, '\n');
if(end) {
for(; ISSPACE(*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("Trailer:", k->p) ||
checkprefix("Trailers:", k->p)) {
conn->bits.trailerHdrPresent = TRUE;
}
else if (checkprefix("Content-Encoding:", k->p) &&
data->set.encoding) {
char *start;
for(start=k->p+17;
*start && ISSPACE(*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 (checkprefix("Content-Range:", k->p)) {
char *ptr = k->p + 14;
while(*ptr && !ISDIGIT(*ptr))
ptr++;
k->offset = curlx_strtoofft(ptr, NULL, 10);
if (data->reqdata.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,
data->reqdata.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 = (long)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(*start ))
start++;
ptr = k->end_ptr-1;
while((ptr>=start) && ISSPACE(*ptr))
ptr--;
ptr++;
backup = *ptr;
if(ptr != start) {
*ptr = '\0';
data->reqdata.newurl = strdup(start);
*ptr = backup;
if(!data->reqdata.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, (size_t)k->hbuflen, conn);
result = Curl_client_write(conn, writetype, k->p, k->hbuflen);
if(result)
return result;
data->info.header_size += (long)k->hbuflen;
data->reqdata.keep.headerbytecount += (long)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 (data->reqdata.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 (data->reqdata.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 && !data->reqdata.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,
(size_t)k->hbuflen, conn);
if(k->badheader == HEADER_PARTHEADER)
Curl_debug(data, CURLINFO_DATA_IN,
k->str, (size_t)nread, conn);
}
else
Curl_debug(data, CURLINFO_DATA_IN,
k->str, (size_t)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) {
size_t dataleft;
k->keepon &= ~KEEP_READ;
dataleft = data->reqdata.proto.http->chunk.dataleft;
if (dataleft != 0) {
infof(conn->data, "Leftovers after chunking. "
" Rewinding %d bytes\n",dataleft);
read_rewind(conn, dataleft);
}
}
}
#endif
if((-1 != k->maxdownload) &&
(k->bytecount + nread >= k->maxdownload)) {
size_t excess = (size_t)(k->bytecount + nread - k->maxdownload);
if (excess > 0 && !k->ignorebody) {
infof(data,
"Rewinding stream by : %d"
" bytes on url %s (size = %" FORMAT_OFF_T
", maxdownload = %" FORMAT_OFF_T
", bytecount = %" FORMAT_OFF_T ", nread = %d)\n",
excess, conn->data->reqdata.path,
k->size, k->maxdownload, k->bytecount, nread);
read_rewind(conn, excess);
}
nread = (ssize_t) (k->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(conn, CLIENTWRITE_BODY,
data->state.headerbuff,
k->hbuflen);
if(result)
return result;
}
if(k->badheader < HEADER_ALLBAD) {
#ifdef HAVE_LIBZ
switch (conn->data->set.http_ce_skip ?
IDENTITY : k->content_encoding) {
case IDENTITY:
#endif
if(!k->ignorebody)
result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str,
nread);
#ifdef HAVE_LIBZ
break;
case DEFLATE:
if(!k->ignorebody)
result = Curl_unencode_deflate_write(conn, k, nread);
break;
case GZIP:
if(!k->ignorebody)
result = Curl_unencode_gzip_write(conn, 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 & CURL_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 == data->reqdata.upload_present) {
data->reqdata.upload_fromhere = k->uploadbuf;
if(!k->upload_done) {
int fillcount;
if(k->wait100_after_headers &&
(data->reqdata.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;
}
data->reqdata.upload_present = nread;
#ifdef CURL_DO_LINEEND_CONV
if ((data->set.crlf) || (data->set.prefer_ascii)) {
#else
if (data->set.crlf) {
#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;
}
for(i = 0, si = 0; i < nread; i++, si++) {
if (data->reqdata.upload_fromhere[i] == 0x0a) {
data->state.scratch[si++] = 0x0d;
data->state.scratch[si] = 0x0a;
if (!data->set.crlf) {
data->set.infilesize++;
}
}
else
data->state.scratch[si] = data->reqdata.upload_fromhere[i];
}
if(si != nread) {
nread = si;
data->reqdata.upload_fromhere = data->state.scratch;
data->reqdata.upload_present = nread;
}
}
}
else {
}
result = Curl_write(conn,
conn->writesockfd,
data->reqdata.upload_fromhere,
data->reqdata.upload_present,
&bytes_written);
if(result)
return result;
if(data->set.verbose)
Curl_debug(data, CURLINFO_DATA_OUT, data->reqdata.upload_fromhere,
(size_t)bytes_written, conn);
if(data->reqdata.upload_present != bytes_written) {
data->reqdata.upload_present -= bytes_written;
data->reqdata.upload_fromhere += bytes_written;
writedone = TRUE;
}
else {
data->reqdata.upload_fromhere = k->uploadbuf;
data->reqdata.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(k->bytecountp)
*k->bytecountp = k->bytecount;
if(k->writebytecountp)
*k->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) >= data->set.timeout)) {
if (k->size != -1) {
failf(data, "Operation timed out after %ld milliseconds with %"
FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received",
data->set.timeout, k->bytecount, k->size);
} else {
failf(data, "Operation timed out after %ld milliseconds with %"
FORMAT_OFF_T " bytes received",
data->set.timeout, k->bytecount);
}
return CURLE_OPERATION_TIMEOUTED;
}
if(!k->keepon) {
if(!(conn->bits.no_body) && (k->size != -1) &&
(k->bytecount != k->size) &&
#ifdef CURL_DO_LINEEND_CONV
(k->bytecount != (k->size + data->state.crlf_conversions)) &&
#endif
!data->reqdata.newurl) {
failf(data, "transfer closed with %" FORMAT_OFF_T
" bytes remaining to read",
k->size - k->bytecount);
return CURLE_PARTIAL_FILE;
}
else if(!(conn->bits.no_body) &&
conn->bits.chunk &&
(data->reqdata.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 = (bool)(0 == (k->keepon&(KEEP_READ|KEEP_WRITE)));
return CURLE_OK;
}
CURLcode Curl_readwrite_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct Curl_transfer_keeper *k = &data->reqdata.keep;
memset(k, 0, sizeof(struct Curl_transfer_keeper));
k->start = Curl_tvnow();
k->now = k->start;
k->header = TRUE;
k->httpversion = -1;
k->size = data->reqdata.size;
k->maxdownload = data->reqdata.maxdownload;
k->bytecountp = data->reqdata.bytecountp;
k->writebytecountp = data->reqdata.writebytecountp;
k->bytecount = 0;
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(k->size > 0)
Curl_pgrsSetDownloadSize(data, k->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->state.expect100header &&
(data->reqdata.proto.http->sending == HTTPSEND_BODY)) {
k->write_after_100_header = TRUE;
k->start100 = k->start;
}
else {
if(data->state.expect100header)
k->wait100_after_headers = TRUE;
k->keepon |= KEEP_WRITE;
}
}
}
return CURLE_OK;
}
void Curl_pre_readwrite(struct connectdata *conn)
{
DEBUGF(infof(conn->data, "Pre readwrite setting chunky header "
"values to default\n"));
conn->bits.chunk=FALSE;
conn->bits.trailerHdrPresent=FALSE;
}
int Curl_single_getsock(struct connectdata *conn,
curl_socket_t *sock,
int numsocks)
{
struct SessionHandle *data = conn->data;
int bitmap = GETSOCK_BLANK;
int index = 0;
if(numsocks < 2)
return GETSOCK_BLANK;
if(data->reqdata.keep.keepon & KEEP_READ) {
bitmap |= GETSOCK_READSOCK(index);
sock[index] = conn->sockfd;
}
if(data->reqdata.keep.keepon & KEEP_WRITE) {
if((conn->sockfd != conn->writesockfd) ||
!(data->reqdata.keep.keepon & KEEP_READ)) {
if(data->reqdata.keep.keepon & KEEP_READ)
index++;
sock[index] = conn->writesockfd;
}
bitmap |= GETSOCK_WRITESOCK(index);
}
return bitmap;
}
static CURLcode
Transfer(struct connectdata *conn)
{
CURLcode result;
struct SessionHandle *data = conn->data;
struct Curl_transfer_keeper *k = &data->reqdata.keep;
bool done=FALSE;
if(!(conn->protocol & (PROT_FILE|PROT_TFTP))) {
Curl_readwrite_init(conn);
Curl_pre_readwrite(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) {
curl_socket_t fd_read;
curl_socket_t fd_write;
if ((k->keepon & KEEP_WRITE) &&
(!data->set.max_send_speed ||
(data->progress.ulspeed < data->set.max_send_speed) )) {
fd_write = conn->writesockfd;
k->keepon &= ~KEEP_WRITE_HOLD;
}
else {
fd_write = CURL_SOCKET_BAD;
if(k->keepon & KEEP_WRITE)
k->keepon |= KEEP_WRITE_HOLD;
}
if ((k->keepon & KEEP_READ) &&
(!data->set.max_recv_speed ||
(data->progress.dlspeed < data->set.max_recv_speed)) ) {
fd_read = conn->sockfd;
k->keepon &= ~KEEP_READ_HOLD;
}
else {
fd_read = CURL_SOCKET_BAD;
if(k->keepon & KEEP_READ)
k->keepon |= KEEP_READ_HOLD;
}
switch (Curl_socket_ready(fd_read, fd_write, 1000)) {
case -1:
#ifdef EINTR
if(SOCKERRNO == 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)
{
CURLcode res;
if(!data->change.url) {
failf(data, "No URL set!\n");
return CURLE_URL_MALFORMAT;
}
res = Curl_ssl_initsessions(data, data->set.ssl.numsessions);
if(res)
return res;
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(data->change.cookielist) {
Curl_cookie_loadfiles(data);
}
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(!retry) {
if ((data->set.maxredirs != -1) &&
(data->set.followlocation >= data->set.maxredirs)) {
failf(data,"Maximum (%d) redirects followed", data->set.maxredirs);
return CURLE_TOO_MANY_REDIRECTS;
}
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 = strchr(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, FALSE);
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;
struct SessionHandle *data = conn->data;
if((data->reqdata.keep.bytecount +
data->reqdata.keep.headerbytecount == 0) &&
conn->bits.reuse &&
!conn->bits.no_body) {
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) {
bool do_done;
if(data->set.connect_only) {
conn->bits.close = FALSE;
res = Curl_done(&conn, CURLE_OK, FALSE);
break;
}
res = Curl_do(&conn, &do_done);
if(res == CURLE_OK) {
res = Transfer(conn);
if(res == CURLE_OK) {
retry = Curl_retry_request(conn, &newurl);
if(!retry)
newurl = data->reqdata.newurl?strdup(data->reqdata.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, FALSE);
if(CURLE_OK == res)
res = res2;
}
else
res2 = Curl_done(&conn, res, FALSE);
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);
if(res && !data->state.errorbuf) {
const char *str = curl_easy_strerror(res);
if(!str)
failf(data, "unspecified error %d", (int)res);
else
failf(data, "%s", str);
}
res2 = Curl_posttransfer(data);
if(!res && res2)
res = res2;
return res;
}
CURLcode
Curl_setup_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;
struct SessionHandle *data;
if(!conn)
return CURLE_BAD_FUNCTION_ARGUMENT;
data = conn->data;
DEBUGASSERT((sockindex <= 1) && (sockindex >= -1));
conn->sockfd = sockindex == -1 ?
CURL_SOCKET_BAD : conn->sock[sockindex];
conn->writesockfd = writesockindex == -1 ?
CURL_SOCKET_BAD:conn->sock[writesockindex];
conn->bits.getheader = getheader;
data->reqdata.size = size;
data->reqdata.bytecountp = bytecountp;
data->reqdata.writebytecountp = writecountp;
return CURLE_OK;
}