Newer
Older
Daniel Stenberg
committed
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
Daniel Stenberg
committed
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
Daniel Stenberg
committed
unsigned char *ap;
unsigned char *pp;
int alen, plen;
char portmsgbuf[4096], tmp[4096];
Daniel Stenberg
committed
const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
char **modep;
Daniel Stenberg
committed
/*
* we should use Curl_if2ip? given pickiness of recent ftpd,
* I believe we should use the same address as the control connection.
*/
sslen = sizeof(ss);
if (getsockname(conn->firstsocket, (struct sockaddr *)&ss, &sslen) < 0)
return CURLE_FTP_PORT_FAILED;
if (getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, 0,
niflags))
return CURLE_FTP_PORT_FAILED;
Daniel Stenberg
committed
memset(&hints, 0, sizeof(hints));
hints.ai_family = sa->sa_family;
/*hints.ai_family = ss.ss_family;
this way can be used if sockaddr_storage is properly defined, as glibc
2.1.X doesn't do*/
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(hbuf, (char *)"0", &hints, &res))
Daniel Stenberg
committed
return CURLE_FTP_PORT_FAILED;
portsock = -1;
for (ai = res; ai; ai = ai->ai_next) {
portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (portsock < 0)
continue;
Daniel Stenberg
committed
if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
sclose(portsock);
portsock = -1;
continue;
Daniel Stenberg
committed
if (listen(portsock, 1) < 0) {
sclose(portsock);
portsock = -1;
continue;
Daniel Stenberg
committed
break;
}
Daniel Stenberg
committed
if (portsock < 0) {
failf(data, strerror(errno));
return CURLE_FTP_PORT_FAILED;
}
Daniel Stenberg
committed
sslen = sizeof(ss);
if (getsockname(portsock, sa, &sslen) < 0) {
failf(data, strerror(errno));
return CURLE_FTP_PORT_FAILED;
}
Daniel Stenberg
committed
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
for (modep = (char **)mode; modep && *modep; modep++) {
int lprtaf, eprtaf;
switch (sa->sa_family) {
case AF_INET:
ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
lprtaf = 4;
eprtaf = 1;
break;
case AF_INET6:
ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
lprtaf = 6;
eprtaf = 2;
break;
default:
ap = pp = NULL;
lprtaf = eprtaf = -1;
break;
}
Daniel Stenberg
committed
if (strcmp(*modep, "EPRT") == 0) {
if (eprtaf < 0)
continue;
if (getnameinfo((struct sockaddr *)&ss, sslen,
portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp), niflags))
continue;
Sterling Hughes
committed
Daniel Stenberg
committed
/* do not transmit IPv6 scope identifier to the wire */
if (sa->sa_family == AF_INET6) {
char *q = strchr(portmsgbuf, '%');
Sterling Hughes
committed
if (q)
*q = '\0';
Daniel Stenberg
committed
}
Sterling Hughes
committed
result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", *modep, eprtaf,
portmsgbuf, tmp);
if(result)
return result;
Daniel Stenberg
committed
} else if (strcmp(*modep, "LPRT") == 0 ||
strcmp(*modep, "PORT") == 0) {
int i;
if (strcmp(*modep, "LPRT") == 0 && lprtaf < 0)
continue;
if (strcmp(*modep, "PORT") == 0 && sa->sa_family != AF_INET)
continue;
Daniel Stenberg
committed
portmsgbuf[0] = '\0';
if (strcmp(*modep, "LPRT") == 0) {
snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
Sterling Hughes
committed
continue;
}
Daniel Stenberg
committed
}
Sterling Hughes
committed
Daniel Stenberg
committed
for (i = 0; i < alen; i++) {
if (portmsgbuf[0])
snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
else
snprintf(tmp, sizeof(tmp), "%u", ap[i]);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
continue;
Sterling Hughes
committed
}
Daniel Stenberg
committed
}
if (strcmp(*modep, "LPRT") == 0) {
snprintf(tmp, sizeof(tmp), ",%d", plen);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
continue;
}
Sterling Hughes
committed
Daniel Stenberg
committed
for (i = 0; i < plen; i++) {
snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
continue;
Sterling Hughes
committed
}
Daniel Stenberg
committed
result = Curl_ftpsendf(conn, "%s %s", *modep, portmsgbuf);
if(result)
return result;
Daniel Stenberg
committed
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if (ftpcode != 200) {
failf(data, "Server does not grok %s", *modep);
continue;
Daniel Stenberg
committed
else
break;
}
if (!*modep) {
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
/* we set the secondary socket variable to this for now, it
is only so that the cleanup function will close it in case
we fail before the true secondary stuff is made */
conn->secondarysocket = portsock;
Daniel Stenberg
committed
/******************************************************************
*
* Here's a piece of IPv4-specific code coming up
*
*/
struct sockaddr_in sa;
struct hostent *h=NULL;
char *hostdataptr=NULL;
unsigned short porttouse;
char myhost[256] = "";
if(data->set.ftpport) {
if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
h = Curl_resolv(data, myhost, 0, &hostdataptr);
Daniel Stenberg
committed
else {
int len = strlen(data->set.ftpport);
if(len>1)
h = Curl_resolv(data, data->set.ftpport, 0, &hostdataptr);
Daniel Stenberg
committed
if(h)
strcpy(myhost, data->set.ftpport); /* buffer overflow risk */
Daniel Stenberg
committed
}
if(! *myhost) {
char *tmp_host = getmyhost(myhost, sizeof(myhost));
h=Curl_resolv(data, tmp_host, 0, &hostdataptr);
Daniel Stenberg
committed
}
infof(data, "We connect from %s\n", myhost);
if ( h ) {
if( (portsock = socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) {
Daniel Stenberg
committed
/* we set the secondary socket variable to this for now, it
is only so that the cleanup function will close it in case
we fail before the true secondary stuff is made */
conn->secondarysocket = portsock;
memset((char *)&sa, 0, sizeof(sa));
memcpy((char *)&sa.sin_addr,
h->h_addr,
h->h_length);
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = INADDR_ANY;
sa.sin_port = 0;
size = sizeof(sa);
if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
/* we succeeded to bind */
struct sockaddr_in add;
Daniel Stenberg
committed
socklen_t socksize = sizeof(add);
Daniel Stenberg
committed
if(getsockname(portsock, (struct sockaddr *) &add,
Daniel Stenberg
committed
&socksize)<0) {
Daniel Stenberg
committed
failf(data, "getsockname() failed");
return CURLE_FTP_PORT_FAILED;
Daniel Stenberg
committed
porttouse = ntohs(add.sin_port);
if ( listen(portsock, 1) < 0 ) {
failf(data, "listen(2) failed on socket");
Daniel Stenberg
committed
failf(data, "bind(2) failed on socket");
Daniel Stenberg
committed
failf(data, "socket(2) failed (%s)");
free(hostdataptr);
Daniel Stenberg
committed
}
else {
failf(data, "could't find my own IP address (%s)", myhost);
return CURLE_FTP_PORT_FAILED;
}
{
#ifdef HAVE_INET_NTOA_R
char ntoa_buf[64];
#endif
struct in_addr in;
unsigned short ip[5];
(void) memcpy(&in.s_addr, *h->h_addr_list, sizeof (in.s_addr));
#ifdef HAVE_INET_NTOA_R
/* ignore the return code from inet_ntoa_r() as it is int or
char * depending on system */
inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf));
sscanf( ntoa_buf, "%hu.%hu.%hu.%hu",
&ip[0], &ip[1], &ip[2], &ip[3]);
Daniel Stenberg
committed
sscanf( inet_ntoa(in), "%hu.%hu.%hu.%hu",
&ip[0], &ip[1], &ip[2], &ip[3]);
Daniel Stenberg
committed
result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
ip[0], ip[1], ip[2], ip[3],
porttouse >> 8,
porttouse & 255);
if(result)
return result;
Daniel Stenberg
committed
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg
committed
if(ftpcode != 200) {
failf(data, "Server does not grok PORT, try without it!");
return CURLE_FTP_PORT_FAILED;
Daniel Stenberg
committed
#endif /* end of ipv4-specific code */
return CURLE_OK;
}
/***********************************************************************
*
* ftp_use_pasv()
*
* Send the PASV command. PASV is the ftp client's way of asking the server to
* open a second port that we can connect to (for the data transfer). This is
* the opposite of PORT.
Daniel Stenberg
committed
*/
static
CURLcode ftp_use_pasv(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
ssize_t nread;
char *buf = data->state.buffer; /* this is our buffer */
int ftpcode; /* receive FTP response codes in this */
CURLcode result;
Curl_addrinfo *addr=NULL;
Curl_ipconnect *conninfo;
/*
Here's the excecutive summary on what to do:
PASV is RFC959, expect:
227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
LPSV is RFC1639, expect:
228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
EPSV is RFC2428, expect:
229 Entering Extended Passive Mode (|||port|)
*/
Daniel Stenberg
committed
#if 1
const char *mode[] = { "EPSV", "PASV", NULL };
int results[] = { 229, 227, 0 };
#else
Daniel Stenberg
committed
char *mode[] = { "EPSV", "LPSV", "PASV", NULL };
int results[] = { 229, 228, 227, 0 };
Daniel Stenberg
committed
const char *mode[] = { "PASV", NULL };
int results[] = { 227, 0 };
#endif
Daniel Stenberg
committed
int modeoff;
unsigned short connectport; /* the local port connect() should use! */
unsigned short newport; /* remote port, not necessary the local one */
char *hostdataptr=NULL;
/* newhost must be able to hold a full IP-style address in ASCII, which
in the IPv6 case means 5*8-1 = 39 letters */
char newhost[48];
char *newhostp=NULL;
Daniel Stenberg
committed
for (modeoff = (data->set.ftp_use_epsv?0:1);
mode[modeoff]; modeoff++) {
result = Curl_ftpsendf(conn, mode[modeoff]);
if(result)
return result;
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if (ftpcode == results[modeoff])
break;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
if (!mode[modeoff]) {
failf(data, "Odd return code after PASV");
return CURLE_FTP_WEIRD_PASV_REPLY;
}
else if (227 == results[modeoff]) {
Daniel Stenberg
committed
int ip[4];
int port[2];
char *str=buf;
Daniel Stenberg
committed
/*
* New 227-parser June 3rd 1999.
* It now scans for a sequence of six comma-separated numbers and
* will take them as IP+port indicators.
*
* Found reply-strings include:
* "227 Entering Passive Mode (127,0,0,1,4,51)"
* "227 Data transfer will passively listen to 127,0,0,1,4,51"
* "227 Entering passive mode. 127,0,0,1,4,51"
*/
Daniel Stenberg
committed
while(*str) {
if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
&ip[0], &ip[1], &ip[2], &ip[3],
&port[0], &port[1]))
break;
str++;
}
Sterling Hughes
committed
Daniel Stenberg
committed
if(!*str) {
failf(data, "Couldn't interpret this 227-reply: %s", buf);
return CURLE_FTP_WEIRD_227_FORMAT;
}
Daniel Stenberg
committed
sprintf(newhost, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
newhostp = newhost;
Daniel Stenberg
committed
newport = (port[0]<<8) + port[1];
#if 1
char *ptr = strchr(buf, '(');
if(ptr) {
unsigned int num;
char separator[4];
ptr++;
if(5 == sscanf(ptr, "%c%c%c%u%c",
&separator[0],
&separator[1],
&separator[2],
&num,
&separator[3])) {
/* the four separators should be identical */
newport = num;
/* we should use the same host we already are connected to */
newhostp = conn->name;
}
else
ptr=NULL;
Daniel Stenberg
committed
}
if(!ptr) {
failf(data, "Weirdly formatted EPSV reply");
return CURLE_FTP_WEIRD_PASV_REPLY;
Daniel Stenberg
committed
}
}
#endif
else
return CURLE_FTP_CANT_RECONNECT;
if(data->change.proxy) {
/*
* This is a tunnel through a http proxy and we need to connect to the
* proxy again here. We already have the name info for it since the
* previous lookup.
*/
addr = conn->hostaddr;
connectport =
(unsigned short)conn->port; /* we connect to the proxy's port */
}
else {
/* normal, direct, ftp connection */
addr = Curl_resolv(data, newhostp, newport, &hostdataptr);
if(!addr) {
failf(data, "Can't resolve new host %s", newhost);
return CURLE_FTP_CANT_GET_HOST;
}
connectport = newport; /* we connect to the remote port */
}
Daniel Stenberg
committed
result = Curl_connecthost(conn,
addr,
connectport,
&conn->secondarysocket,
&conninfo);
if((CURLE_OK == result) &&
data->set.verbose)
/* this just dumps information about this second connection */
ftp_pasv_verbose(conn, conninfo, newhost, connectport);
if(CURLE_OK != result)
return result;
if (data->set.tunnel_thru_httpproxy) {
/* We want "seamless" FTP operations through HTTP proxy tunnel */
result = Curl_ConnectHTTPProxyTunnel(conn, conn->secondarysocket,
newhost, newport);
Daniel Stenberg
committed
}
return CURLE_OK;
}
/***********************************************************************
*
* ftp_perform()
*
* This is the actual DO function for FTP. Get a file/directory according to
* the options previously setup.
*/
Daniel Stenberg
committed
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
static
CURLcode ftp_perform(struct connectdata *conn)
{
/* this is FTP and no proxy */
ssize_t nread;
CURLcode result;
struct SessionHandle *data=conn->data;
char *buf = data->state.buffer; /* this is our buffer */
/* the ftp struct is already inited in ftp_connect() */
struct FTP *ftp = conn->proto.ftp;
long *bytecountp = ftp->bytecountp;
int ftpcode; /* for ftp status */
/* Send any QUOTE strings? */
if(data->set.quote) {
if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
return result;
}
/* This is a re-used connection. Since we change directory to where the
transfer is taking place, we must now get back to the original dir
where we ended up after login: */
if (conn->bits.reuse) {
if ((result = ftp_cwd(conn, ftp->entrypath)) != CURLE_OK)
return result;
}
/* change directory first! */
if(ftp->dir && ftp->dir[0]) {
if ((result = ftp_cwd(conn, ftp->dir)) != CURLE_OK)
return result;
}
/* Requested time of file? */
if(data->set.get_filetime && ftp->file) {
result = ftp_getfiletime(conn, ftp->file);
if(result)
return result;
}
Daniel Stenberg
committed
/* If we have selected NOBODY and HEADER, it means that we only want file
information. Which in FTP can't be much more than the file size and
date. */
if(data->set.no_body && data->set.include_header) {
Daniel Stenberg
committed
/* The SIZE command is _not_ RFC 959 specified, and therefor many servers
may not support it! It is however the only way we have to get a file's
size! */
ssize_t filesize;
/* Some servers return different sizes for different modes, and thus we
must set the proper type before we check the size */
result = ftp_transfertype(conn, data->set.ftp_ascii);
if(result)
return result;
/* failing to get size is not a serious error */
result = ftp_getsize(conn, ftp->file, &filesize);
if(CURLE_OK == result) {
sprintf(buf, "Content-Length: %d\r\n", filesize);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
Daniel Stenberg
committed
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
/* If we asked for a time of the file and we actually got one as
well, we "emulate" a HTTP-style header in our output. */
#ifdef HAVE_STRFTIME
if(data->set.get_filetime && data->info.filetime) {
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm buffer;
tm = (struct tm *)localtime_r(&data->info.filetime, &buffer);
#else
tm = localtime(&data->info.filetime);
#endif
/* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S %Z\r\n",
tm);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
#endif
return CURLE_OK;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(data->set.no_body)
/* don't transfer the data */
;
Daniel Stenberg
committed
/* Get us a second connection up and connected */
Daniel Stenberg
committed
else if(data->set.ftp_use_port) {
Daniel Stenberg
committed
/* We have chosen to use the PORT command */
result = ftp_use_port(conn);
Daniel Stenberg
committed
if(CURLE_OK == result)
/* we have the data connection ready */
infof(data, "Connected the data stream with PORT!\n");
}
else {
Daniel Stenberg
committed
/* We have chosen (this is default) to use the PASV command */
result = ftp_use_pasv(conn);
Daniel Stenberg
committed
if(CURLE_OK == result)
infof(data, "Connected the data stream with PASV!\n");
}
Daniel Stenberg
committed
if(result)
return result;
Daniel Stenberg
committed
if(data->set.upload) {
/* Set type to binary (unless specified ASCII) */
Daniel Stenberg
committed
result = ftp_transfertype(conn, data->set.ftp_ascii);
Daniel Stenberg
committed
if(result)
return result;
if(conn->resume_from) {
/* we're about to continue the uploading of a file */
/* 1. get already existing file's size. We use the SIZE
command for this which may not exist in the server!
The SIZE command is not in RFC959. */
/* 2. This used to set REST. But since we can do append, we
don't another ftp command. We just skip the source file
offset and then we APPEND the rest on the file instead */
/* 3. pass file-size number of bytes in the source file */
/* 4. lower the infilesize counter */
/* => transfer as usual */
if(conn->resume_from < 0 ) {
/* we could've got a specified offset from the command line,
but now we know we didn't */
ssize_t gottensize;
if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) {
Daniel Stenberg
committed
failf(data, "Couldn't get remote file size");
conn->resume_from = gottensize;
if(conn->resume_from) {
/* do we still game? */
int passed=0;
/* enable append instead */
Daniel Stenberg
committed
data->set.ftp_append = 1;
/* Now, let's read off the proper amount of bytes from the
input. If we knew it was a proper file we could've just
fseek()ed but we only have a stream here */
do {
int readthisamountnow = (conn->resume_from - passed);
int actuallyread;
if(readthisamountnow > BUFSIZE)
readthisamountnow = BUFSIZE;
actuallyread =
Daniel Stenberg
committed
data->set.fread(data->state.buffer, 1, readthisamountnow,
data->set.in);
passed += actuallyread;
if(actuallyread != readthisamountnow) {
failf(data, "Could only read %d bytes from the input", passed);
while(passed != conn->resume_from);
Daniel Stenberg
committed
if(data->set.infilesize>0) {
data->set.infilesize -= conn->resume_from;
Daniel Stenberg
committed
if(data->set.infilesize <= 0) {
infof(data, "File already completely uploaded\n");
/* no data to transfer */
result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
/* Set resume done so that we won't get any error in
* Curl_ftp_done() because we didn't transfer the amount of bytes
* that the local file file obviously is */
conn->bits.resume_done = TRUE;
return CURLE_OK;
}
}
/* we've passed, proceed as normal */
}
}
Daniel Stenberg
committed
/* Send everything on data->set.in to the socket */
Daniel Stenberg
committed
if(data->set.ftp_append) {
/* we append onto the file instead of rewriting it */
Daniel Stenberg
committed
FTPSENDF(conn, "APPE %s", ftp->file);
}
else {
FTPSENDF(conn, "STOR %s", ftp->file);
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode>=400) {
failf(data, "Failed FTP upload:%s", buf+3);
/* oops, we never close the sockets! */
return CURLE_FTP_COULDNT_STOR_FILE;
Daniel Stenberg
committed
if(data->set.ftp_use_port) {
Daniel Stenberg
committed
/* PORT means we are now awaiting the server to connect to us. */
Daniel Stenberg
committed
result = AllowServerConnect(data, conn, conn->secondarysocket);
if( result )
return result;
}
*bytecountp=0;
/* When we know we're uploading a specified file, we can get the file
size prior to the actual upload. */
Daniel Stenberg
committed
Curl_pgrsSetUploadSize(data, data->set.infilesize);
Daniel Stenberg
committed
result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */
conn->secondarysocket, bytecountp);
Daniel Stenberg
committed
else if(!data->set.no_body) {
/* Retrieve file or directory */
bool dirlist=FALSE;
long downloadsize=-1;
if(conn->bits.use_range && conn->range) {
int totalsize=-1;
char *ptr;
char *ptr2;
from=strtol(conn->range, &ptr, 0);
while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
ptr++;
to=strtol(ptr, &ptr2, 0);
if(ptr == ptr2) {
/* we didn't get any digit */
to=-1;
}
conn->resume_from = from;
infof(data, "FTP RANGE %d to end of file\n", from);
conn->maxdownload = -from;
conn->resume_from = from;
infof(data, "FTP RANGE the last %d bytes\n", totalsize);
conn->maxdownload = totalsize+1; /* include the last mentioned byte */
conn->resume_from = from;
infof(data, "FTP RANGE from %d getting %d bytes\n", from,
conn->maxdownload);
}
infof(data, "range-download from %d to %d, totally %d bytes\n",
from, to, totalsize);
}
Daniel Stenberg
committed
if((data->set.ftp_list_only) || !ftp->file) {
/* The specified path ends with a slash, and therefore we think this
is a directory that is requested, use LIST. But before that we
need to set ASCII transfer mode. */
dirlist = TRUE;
/* Set type to ASCII */
Daniel Stenberg
committed
result = ftp_transfertype(conn, TRUE /* ASCII enforced */);
Daniel Stenberg
committed
if(result)
return result;
/* if this output is to be machine-parsed, the NLST command will be
better used since the LIST command output is not specified or
standard in any way */
Daniel Stenberg
committed
FTPSENDF(conn, "%s",
Daniel Stenberg
committed
data->set.customrequest?data->set.customrequest:
(data->set.ftp_list_only?"NLST":"LIST"));
ssize_t foundsize;
Daniel Stenberg
committed
result = ftp_transfertype(conn, data->set.ftp_ascii);
Daniel Stenberg
committed
if(result)
return result;
/* Attempt to get the size, it'll be useful in some cases: for resumed
downloads and when talking to servers that don't give away the size
in the RETR response line. */
result = ftp_getsize(conn, ftp->file, &foundsize);
if(CURLE_OK == result)
downloadsize = foundsize;
if(conn->resume_from) {
/* Daniel: (August 4, 1999)
*
* We start with trying to use the SIZE command to figure out the size
* of the file we're gonna get. If we can get the size, this is by far
* the best way to know if we're trying to resume beyond the EOF.
*
* Daniel, November 28, 2001. We *always* get the size on downloads
* now, so it is done before this even when not doing resumes. I saved
* the comment above for nostalgical reasons! ;-)
*/
Daniel Stenberg
committed
if(CURLE_OK != result) {
infof(data, "ftp server doesn't support SIZE\n");
/* We couldn't get the size and therefore we can't know if there
really is a part of the file left to get, although the server
will just close the connection when we start the connection so it
won't cause us any harm, just not make us exit as nicely. */
}
else {
/* We got a file size report, so we check that there actually is a
part of the file left to get, or else we go home. */
if(conn->resume_from< 0) {
/* We're supposed to download the last abs(from) bytes */
if(foundsize < -conn->resume_from) {
failf(data, "Offset (%d) was beyond file size (%d)",
conn->resume_from, foundsize);
return CURLE_FTP_BAD_DOWNLOAD_RESUME;
}
/* convert to size to download */
downloadsize = -conn->resume_from;
conn->resume_from = foundsize - downloadsize;
if(foundsize < conn->resume_from) {
failf(data, "Offset (%d) was beyond file size (%d)",
conn->resume_from, foundsize);
return CURLE_FTP_BAD_DOWNLOAD_RESUME;
}
/* Now store the number of bytes we are expected to download */
downloadsize = foundsize-conn->resume_from;
Sterling Hughes
committed
if (downloadsize == 0) {
/* no data to transfer */
result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
Sterling Hughes
committed
infof(data, "File already completely downloaded\n");
/* Set resume done so that we won't get any error in Curl_ftp_done()
* because we didn't transfer the amount of bytes that the remote
* file obviously is */
conn->bits.resume_done = TRUE;
Sterling Hughes
committed
return CURLE_OK;
}
/* Set resume file transfer offset */
infof(data, "Instructs server to resume from offset %d\n",
conn->resume_from);
Daniel Stenberg
committed
FTPSENDF(conn, "REST %d", conn->resume_from);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode != 350) {
Daniel Stenberg
committed
FTPSENDF(conn, "RETR %s", ftp->file);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
Daniel Stenberg
committed
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if((ftpcode == 150) || (ftpcode == 125)) {
/*
A;
150 Opening BINARY mode data connection for /etc/passwd (2241
bytes). (ok, the file is being transfered)
B:
150 Opening ASCII mode data connection for /bin/ls
C:
150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
D:
150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
E:
125 Data connection already open; Transfer starting. */
int size=-1; /* default unknown size */
Daniel Stenberg
committed
if(!dirlist &&
Daniel Stenberg
committed
!data->set.ftp_ascii &&
Daniel Stenberg
committed
(-1 == downloadsize)) {
/*
* It seems directory listings either don't show the size or very
Daniel Stenberg
committed
* often uses size 0 anyway. ASCII transfers may very well turn out
* that the transfered amount of data is not the same as this line
* tells, why using this number in those cases only confuses us.
*
* Example D above makes this parsing a little tricky */
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
char *bytes;
bytes=strstr(buf, " bytes");
if(bytes--) {
int index=bytes-buf;
/* this is a hint there is size information in there! ;-) */
while(--index) {
/* scan for the parenthesis and break there */
if('(' == *bytes)
break;
/* if only skip digits, or else we're in deep trouble */
if(!isdigit((int)*bytes)) {
bytes=NULL;
break;
}
/* one more estep backwards */
bytes--;
}
/* only if we have nothing but digits: */
if(bytes++) {
/* get the number! */
size = atoi(bytes);
}
}
}
else if(downloadsize > -1)
size = downloadsize;
Daniel Stenberg
committed
if(data->set.ftp_use_port) {
Daniel Stenberg
committed
result = AllowServerConnect(data, conn, conn->secondarysocket);
if( result )
return result;
}
infof(data, "Getting file with size: %d\n", size);
/* FTP download: */
result=Curl_Transfer(conn, conn->secondarysocket, size, FALSE,
bytecountp,
-1, NULL); /* no upload here */
if(result)
return result;
}
else {
failf(data, "%s", buf+4);
return CURLE_FTP_COULDNT_RETR_FILE;
/***********************************************************************
*
* Curl_ftp()
*
* This function is registered as 'curl_do' function. It decodes the path
* parts etc as a wrapper to the actual DO function (ftp_perform).
*
* The input argument is already checked for validity.
*/
CURLcode Curl_ftp(struct connectdata *conn)
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
struct FTP *ftp;
int dirlength=0; /* 0 forces strlen() */
/* the ftp struct is already inited in ftp_connect() */
/* We split the path into dir and file parts *before* we URLdecode
it */
ftp->file = strrchr(conn->ppath, '/');
if(ftp->file) {
Daniel Stenberg
committed
if(ftp->file != conn->ppath)
dirlength=ftp->file-conn->ppath; /* don't count the traling slash */
ftp->file++; /* point to the first letter in the file name part or
remain NULL */
}