#include "setup.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "strequal.h"
#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#include <winsock.h>
#include <time.h>
#include <io.h>
#else
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/resource.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
#include <sys/ioctl.h>
#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_SELECT
#error "We can't compile without select() support!"
#endif
#ifndef HAVE_SOCKET
#error "We can't compile without socket() support!"
#endif
#endif
#include "urldata.h"
#include <curl/curl.h>
#include <curl/types.h>
#include "netrc.h"
#include "hostip.h"
#include "transfer.h"
#include "sendf.h"
#include "speedcheck.h"
#include "getpass.h"
#include "progress.h"
#include "getdate.h"
#include "http.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#ifdef MALLOCDEBUG
#include "memdebug.h"
#endif
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
CURLcode static
Transfer(struct connectdata *c_conn)
{
ssize_t nread;
int bytecount = 0;
int writebytecount = 0;
long contentlength=0;
struct timeval start = Curl_tvnow();
struct timeval now = start;
bool header = TRUE;
int headerline = 0;
char *hbufp;
int hbuflen = 0;
char *str;
char *str_start;
char *end_ptr;
char *p;
bool content_range = FALSE;
int offset = 0;
int httpcode = 0;
int httpversion = -1;
CURLcode urg;
time_t timeofdoc=0;
long bodywrites=0;
int writetype;
struct UrlData *data;
struct connectdata *conn = (struct connectdata *)c_conn;
char *buf;
int maxfd;
data = conn->data;
buf = data->buffer;
maxfd = (conn->sockfd>conn->writesockfd?conn->sockfd:conn->writesockfd)+1;
hbufp = data->headerbuff;
myalarm (0);
now = Curl_tvnow();
start = now;
#define KEEP_READ 1
#define KEEP_WRITE 2
Curl_pgrsTime(data, TIMER_PRETRANSFER);
Curl_speedinit(data);
if((conn->sockfd == -1) &&
(conn->writesockfd == -1)) {
return CURLE_OK;
}
if (!conn->getheader) {
header = FALSE;
if(conn->size > 0)
Curl_pgrsSetDownloadSize(data, conn->size);
}
if(conn->getheader ||
!data->bits.no_body) {
fd_set readfd;
fd_set writefd;
fd_set rkeepfd;
fd_set wkeepfd;
struct timeval interval;
int keepon=0;
FD_ZERO (&readfd);
if(conn->sockfd != -1) {
FD_SET (conn->sockfd, &readfd);
keepon |= KEEP_READ;
}
FD_ZERO (&writefd);
if(conn->writesockfd != -1) {
FD_SET (conn->writesockfd, &writefd);
keepon |= KEEP_WRITE;
}
rkeepfd = readfd;
wkeepfd = writefd;
while (keepon) {
readfd = rkeepfd;
writefd = wkeepfd;
interval.tv_sec = 1;
interval.tv_usec = 0;
switch (select (maxfd, &readfd, &writefd, NULL, &interval)) {
case -1:
#ifdef EINTR
if(errno == EINTR)
;
else
#endif
keepon = 0;
continue;
case 0:
break;
default:
if((keepon & KEEP_READ) && FD_ISSET(conn->sockfd, &readfd)) {
urg = Curl_read(conn, conn->sockfd, buf, BUFSIZE -1, &nread);
if (0 < (signed int) nread)
buf[nread] = 0;
else if (0 >= (signed int) nread) {
keepon &= ~KEEP_READ;
break;
}
str = buf;
if (header) {
do {
int hbufp_index;
str_start = str;
end_ptr = strchr (str_start, '\n');
if (!end_ptr) {
int str_length = (int)strlen(str);
if (hbuflen + (int)str_length >= data->headersize) {
char *newbuff;
long newsize=MAX((hbuflen+str_length)*3/2,
data->headersize*2);
hbufp_index = hbufp - data->headerbuff;
newbuff = (char *)realloc(data->headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return CURLE_READ_ERROR;
}
data->headersize=newsize;
data->headerbuff = newbuff;
hbufp = data->headerbuff + hbufp_index;
}
strcpy (hbufp, str);
hbufp += strlen (str);
hbuflen += strlen (str);
break;
}
str = end_ptr + 1;
if (hbuflen + (str - str_start) >= data->headersize) {
char *newbuff;
long newsize=MAX((hbuflen+(str-str_start))*3/2,
data->headersize*2);
hbufp_index = hbufp - data->headerbuff;
newbuff = (char *)realloc(data->headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return CURLE_READ_ERROR;
}
data->headersize= newsize;
data->headerbuff = newbuff;
hbufp = data->headerbuff + hbufp_index;
}
strncpy (hbufp, str_start, str - str_start);
hbufp += str - str_start;
hbuflen += str - str_start;
*hbufp = 0;
p = data->headerbuff;
if (('\n' == *p) || ('\r' == *p)) {
#if 0
if (-1 != conn->size)
conn->size += bytecount;
#endif
if ('\r' == *p)
p++;
if ('\n' == *p)
p++;
#if 0
Curl_pgrsSetDownloadSize(data, conn->size);
#endif
if(100 == httpcode) {
header = TRUE;
headerline = 0;
}
else
header = FALSE;
writetype = CLIENTWRITE_HEADER;
if (data->bits.http_include_header)
writetype |= CLIENTWRITE_BODY;
urg = Curl_client_write(data, writetype, data->headerbuff,
p - data->headerbuff);
if(urg)
return urg;
data->header_size += p - data->headerbuff;
if(!header) {
if(data->bits.no_body)
return CURLE_OK;
break;
}
hbufp = data->headerbuff;
hbuflen = 0;
continue;
}
if (!headerline++) {
if (2 == sscanf (p, " HTTP/1.%d %3d", &httpversion,
&httpcode)) {
if (
( ((data->bits.http_follow_location) &&
(httpcode >= 400))
||
(!data->bits.http_follow_location &&
(httpcode >= 300)))
&& (data->bits.http_fail_on_error)) {
failf (data, "The requested file was not found");
return CURLE_HTTP_NOT_FOUND;
}
data->progress.httpcode = httpcode;
data->progress.httpversion = httpversion;
if(httpversion == 0)
conn->bits.close = TRUE;
}
else {
header = FALSE;
break;
}
}
if (strnequal("Content-Length", p, 14) &&
sscanf (p+14, ": %ld", &contentlength)) {
conn->size = contentlength;
Curl_pgrsSetDownloadSize(data, contentlength);
}
else if((httpversion == 0) &&
conn->bits.httpproxy &&
strnequal("Proxy-Connection: keep-alive", p,
strlen("Proxy-Connection: keep-alive"))) {
conn->bits.close = FALSE;
infof(data, "HTTP/1.0 proxy connection set to keep alive!\n");
}
else if((httpversion == 0) &&
strnequal("Connection: keep-alive", p,
strlen("Connection: keep-alive"))) {
conn->bits.close = FALSE;
infof(data, "HTTP/1.0 connection set to keep alive!\n");
}
else if (strnequal("Connection: close", p,
strlen("Connection: close"))) {
conn->bits.close = TRUE;
}
else if (strnequal("Transfer-Encoding: chunked", p,
strlen("Transfer-Encoding: chunked"))) {
conn->bits.chunk = TRUE;
Curl_httpchunk_init(conn);
}
else if (strnequal("Content-Range", p, 13)) {
if (sscanf (p+13, ": bytes %d-", &offset) ||
sscanf (p+13, ": bytes: %d-", &offset)) {
if (conn->resume_from == offset) {
content_range = TRUE;
}
}
}
else if(data->cookies &&
strnequal("Set-Cookie: ", p, 11)) {
Curl_cookie_add(data->cookies, TRUE, &p[12]);
}
else if(strnequal("Last-Modified:", p,
strlen("Last-Modified:")) &&
(data->timecondition || data->bits.get_filetime) ) {
time_t secs=time(NULL);
timeofdoc = curl_getdate(p+strlen("Last-Modified:"), &secs);
if(data->bits.get_filetime)
data->progress.filetime = timeofdoc;
}
else if ((httpcode >= 300 && httpcode < 400) &&
(data->bits.http_follow_location) &&
strnequal("Location: ", p, 10)) {
char *ptr;
char *start=p;
char backup;
start += 10;
ptr = start;
while(*ptr && !isspace((int)*ptr))
ptr++;
backup = *ptr;
*ptr = '\0';
conn->newurl = strdup(start);
*ptr = backup;
}
writetype = CLIENTWRITE_HEADER;
if (data->bits.http_include_header)
writetype |= CLIENTWRITE_BODY;
urg = Curl_client_write(data, writetype, p, hbuflen);
if(urg)
return urg;
data->header_size += hbuflen;
hbufp = data->headerbuff;
hbuflen = 0;
}
while (*str);
if (!header) {
nread -= (str - buf);
}
}
if (str && !header && ((signed int)nread > 0)) {
if(0 == bodywrites) {
if(conn->protocol&PROT_HTTP) {
if (conn->newurl) {
infof (data, "Follow to new URL: %s\n", conn->newurl);
return CURLE_OK;
}
else if (conn->resume_from &&
!content_range &&
(data->httpreq==HTTPREQ_GET)) {
failf (data, "HTTP server doesn't seem to support "
"byte ranges. Cannot resume.");
return CURLE_HTTP_RANGE_ERROR;
}
else if(data->timecondition && !conn->range) {
if((timeofdoc > 0) && (data->timevalue > 0)) {
switch(data->timecondition) {
case TIMECOND_IFMODSINCE:
default:
if(timeofdoc < data->timevalue) {
infof(data,
"The requested document is not new enough\n");
return CURLE_OK;
}
break;
case TIMECOND_IFUNMODSINCE:
if(timeofdoc > data->timevalue) {
infof(data,
"The requested document is not old enough\n");
return CURLE_OK;
}
break;
}
}
}
if(!conn->bits.close) {
if(-1 != conn->size)
conn->maxdownload = conn->size;
}
}
}
bodywrites++;
if(conn->bits.chunk) {
CHUNKcode res =
Curl_httpchunk_read(conn, str, nread, &nread);
if(CHUNKE_OK < res) {
failf(data, "Receeived problem in the chunky parser");
return CURLE_READ_ERROR;
}
else if(CHUNKE_STOP == res) {
keepon &= ~KEEP_READ;
}
}
if(conn->maxdownload &&
(bytecount + nread >= conn->maxdownload)) {
nread = conn->maxdownload - bytecount;
if((signed int)nread < 0 )
nread = 0;
keepon &= ~KEEP_READ;
}
bytecount += nread;
Curl_pgrsSetDownloadCounter(data, (double)bytecount);
if(! conn->bits.chunk) {
urg = Curl_client_write(data, CLIENTWRITE_BODY, str, nread);
if(urg)
return urg;
}
}
}
if((keepon & KEEP_WRITE) && FD_ISSET(conn->writesockfd, &writefd)) {
char scratch[BUFSIZE * 2];
int i, si;
size_t bytes_written;
if(data->crlf)
buf = data->buffer;
nread = data->fread(buf, 1, conn->upload_bufsize, data->in);
if ((signed int)nread<=0) {
keepon &= ~KEEP_WRITE;
break;
}
writebytecount += nread;
Curl_pgrsSetUploadCounter(data, (double)writebytecount);
if (data->crlf) {
for(i = 0, si = 0; i < (int)nread; i++, si++) {
if (buf[i] == 0x0a) {
scratch[si++] = 0x0d;
scratch[si] = 0x0a;
}
else {
scratch[si] = buf[i];
}
}
nread = si;
buf = scratch;
}
urg = Curl_write(conn, conn->writesockfd, buf, nread,
&bytes_written);
if(nread != bytes_written) {
failf(data, "Failed uploading data");
return CURLE_WRITE_ERROR;
}
}
break;
}
now = Curl_tvnow();
if(Curl_pgrsUpdate(conn))
urg = CURLE_ABORTED_BY_CALLBACK;
else
urg = Curl_speedcheck (data, now);
if (urg)
return urg;
if(data->progress.ulspeed > conn->upload_bufsize) {
conn->upload_bufsize=(long)min(data->progress.ulspeed, BUFSIZE);
}
if (data->timeout && (Curl_tvdiff (now, start) > data->timeout)) {
failf (data, "Operation timed out with %d out of %d bytes received",
bytecount, conn->size);
return CURLE_OPERATION_TIMEOUTED;
}
}
}
if(!(data->bits.no_body) && contentlength &&
(bytecount != contentlength)) {
failf(data, "transfer closed with %d bytes remaining to read",
contentlength-bytecount);
return CURLE_PARTIAL_FILE;
}
else if(conn->bits.chunk && conn->proto.http->chunk.datasize) {
failf(data, "transfer closed with at least %d bytes remaining",
conn->proto.http->chunk.datasize);
return CURLE_PARTIAL_FILE;
}
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
if(conn->bytecountp)
*conn->bytecountp = bytecount;
if(conn->writebytecountp)
*conn->writebytecountp = writebytecount;
return CURLE_OK;
}
CURLcode Curl_perform(CURL *curl)
{
CURLcode res;
struct UrlData *data = (struct UrlData *)curl;
struct connectdata *conn=NULL;
bool port=TRUE;
char *newurl = NULL;
if(!data->url)
return CURLE_URL_MALFORMAT;
data->followlocation=0;
data->bits.this_is_a_follow = FALSE;
Curl_pgrsStartNow(data);
do {
Curl_pgrsTime(data, TIMER_STARTSINGLE);
res = Curl_connect(data, &conn, port);
if(res == CURLE_OK) {
res = Curl_do(conn);
if(res == CURLE_OK) {
res = Transfer(conn);
if(res == CURLE_OK) {
newurl = conn->newurl?strdup(conn->newurl):NULL;
res = Curl_done(conn);
}
}
if((res == CURLE_OK) && newurl) {
char prot[16];
char letter;
port=TRUE;
if (data->maxredirs && (data->followlocation >= data->maxredirs)) {
failf(data,"Maximum (%d) redirects followed", data->maxredirs);
res=CURLE_TOO_MANY_REDIRECTS;
break;
}
data->bits.this_is_a_follow = TRUE;
data->followlocation++;
if(data->bits.http_auto_referer) {
if(data->free_referer) {
free(data->referer);
}
data->referer = strdup(data->url);
data->free_referer = TRUE;
data->bits.http_set_referer = TRUE;
}
if(2 != sscanf(newurl, "%15[^:]://%c", prot, &letter)) {
char *protsep;
char *pathsep;
char *newest;
char *url_clone=strdup(data->url);
if(!url_clone)
return CURLE_OUT_OF_MEMORY;
protsep=strstr(url_clone, "//");
if(!protsep)
protsep=url_clone;
else
protsep+=2;
if('/' != newurl[0]) {
pathsep = strrchr(protsep, '?');
if(pathsep)
*pathsep=0;
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep=0;
}
else {
pathsep = strchr(protsep, '/');
if(pathsep)
*pathsep=0;
}
newest=(char *)malloc( strlen(url_clone) +
1 +
strlen(newurl) + 1);
if(!newest)
return CURLE_OUT_OF_MEMORY;
sprintf(newest, "%s%s%s", url_clone, ('/' == newurl[0])?"":"/",
newurl);
free(newurl);
free(url_clone);
newurl = newest;
}
else {
port = FALSE;
}
if(data->bits.urlstringalloc)
free(data->url);
data->url = newurl;
data->bits.urlstringalloc = TRUE;
infof(data, "Follows Location: to new URL: '%s'\n", data->url);
switch(data->progress.httpcode) {
case 300:
case 301:
case 306:
case 307:
default:
break;
case 302:
case 303:
data->bits.http_post = FALSE;
data->bits.http_formpost = FALSE;
data->httpreq = HTTPREQ_GET;
infof(data, "Disables POST\n");
break;
case 304:
break;
case 305:
break;
}
continue;
}
}
break;
} while(1);
if(newurl)
free(newurl);
if(data->timeout || data->connecttimeout)
myalarm(0);
return res;
}
CURLcode
Curl_Transfer(struct connectdata *c_conn,
int sockfd,
int size,
bool getheader,
long *bytecountp,
int writesockfd,
long *writebytecountp
)
{
struct connectdata *conn = (struct connectdata *)c_conn;
if(!conn)
return CURLE_BAD_FUNCTION_ARGUMENT;
conn->sockfd = sockfd;
conn->size = size;
conn->getheader = getheader;
conn->bytecountp = bytecountp;
conn->writesockfd = writesockfd;
conn->writebytecountp = writebytecountp;
return CURLE_OK;
}