Newer
Older
Daniel Stenberg
committed
if(!dirlist &&
Daniel Stenberg
committed
!data->set.ftp_ascii &&
(downloadsize < 1)) {
/*
* 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 */
char *bytes;
bytes=strstr(buf, " bytes");
if(bytes--) {
/* this is a hint there is size information in there! ;-) */
/* 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 = strtoofft(bytes, NULL, 0);
}
}
}
else if(downloadsize > -1)
size = downloadsize;
Daniel Stenberg
committed
if(data->set.ftp_use_port) {
Daniel Stenberg
committed
result = AllowServerConnect(conn);
Daniel Stenberg
committed
if(conn->ssl[SECONDARYSOCKET].use) {
/* since we only have a plaintext TCP connection here, we must now
do the TLS stuff */
infof(data, "Doing the SSL/TLS handshake on the data stream\n");
result = Curl_SSLConnect(conn, SECONDARYSOCKET);
if(result)
return result;
Daniel Stenberg
committed
}
if(size > conn->maxdownload && conn->maxdownload > 0)
Daniel Stenberg
committed
size = conn->size = conn->maxdownload;
infof(data, "Getting file with size: %Od\n", size);
Daniel Stenberg
committed
result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE,
bytecountp,
-1, NULL); /* no upload here */
if(dirlist && (ftpcode == 450)) {
/* simply no matching files */
ftp->no_transfer = TRUE; /* don't think we should download anything */
}
else {
failf(data, "%s", buf+4);
return CURLE_FTP_COULDNT_RETR_FILE;
}
/***********************************************************************
*
* ftp_perform()
*
* This is the actual DO function for FTP. Get a file/directory according to
* the options previously setup.
*/
static
CURLcode ftp_perform(struct connectdata *conn,
bool *connected) /* for the TCP connect status after
PASV / PORT */
{
/* this is FTP and no proxy */
CURLcode result=CURLE_OK;
struct SessionHandle *data=conn->data;
char *buf = data->state.buffer; /* this is our buffer */
Daniel Stenberg
committed
/* the ftp struct is already inited in Curl_ftp_connect() */
struct FTP *ftp = conn->proto.ftp;
/* 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 && ftp->entrypath) {
if ((result = ftp_cwd_and_mkd(conn, ftp->entrypath)) != CURLE_OK)
return result;
}
{
int i; /* counter for loop */
for (i=0; ftp->dirs[i]; i++) {
/* RFC 1738 says empty components should be respected too, but
that is plain stupid since CWD can't be used with an empty argument */
if ((result = ftp_cwd_and_mkd(conn, ftp->dirs[i])) != CURLE_OK)
return result;
}
}
/* Requested time of file or time-depended transfer? */
if((data->set.get_filetime || data->set.timecondition) &&
ftp->file) {
result = ftp_getfiletime(conn, ftp->file);
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
switch( result )
{
case CURLE_FTP_COULDNT_RETR_FILE:
case CURLE_OK:
if(data->set.timecondition) {
if((data->info.filetime > 0) && (data->set.timevalue > 0)) {
switch(data->set.timecondition) {
case TIMECOND_IFMODSINCE:
default:
if(data->info.filetime < data->set.timevalue) {
infof(data, "The requested document is not new enough\n");
ftp->no_transfer = TRUE; /* mark this to not transfer data */
return CURLE_OK;
}
break;
case TIMECOND_IFUNMODSINCE:
if(data->info.filetime > data->set.timevalue) {
infof(data, "The requested document is not old enough\n");
ftp->no_transfer = TRUE; /* mark this to not transfer data */
return CURLE_OK;
}
break;
} /* switch */
else {
infof(data, "Skipping time comparison\n");
}
break;
default:
return result;
} /* switch */
}
/* 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 && ftp->file) {
/* 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! */
Daniel Stenberg
committed
ssize_t nread;
int ftpcode;
ftp->no_transfer = TRUE; /* this means no actual transfer is made */
/* 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: %Od\r\n", filesize);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
Daniel Stenberg
committed
/* Determine if server can respond to REST command and therefore
whether it can do a range */
FTPSENDF(conn, "REST 0", NULL);
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
if ((CURLE_OK == result) && (ftpcode == 350)) {
result = Curl_client_write(data, CLIENTWRITE_BOTH,
(char *)"Accept-ranges: bytes\r\n", 0);
if(result)
return result;
}
/* 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>=0) ) {
struct tm *tm;
struct tm buffer;
tm = (struct tm *)gmtime_r((time_t *)&data->info.filetime, &buffer);
#else
tm = gmtime((time_t *)&data->info.filetime);
#endif
Daniel Stenberg
committed
/* format: "Tue, 15 Nov 1994 12:45:26" */
strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n",
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
tm);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
#endif
return CURLE_OK;
}
if(data->set.no_body)
/* doesn't really transfer any data */
ftp->no_transfer = TRUE;
/* Get us a second connection up and connected */
else if(data->set.ftp_use_port) {
/* We have chosen to use the PORT command */
result = ftp_use_port(conn);
if(CURLE_OK == result) {
/* we have the data connection ready */
infof(data, "Ordered connect of the data stream with PORT!\n");
*connected = TRUE; /* mark us "still connected" */
}
}
else {
/* We have chosen (this is default) to use the PASV command */
result = ftp_use_pasv(conn, connected);
if(CURLE_OK == result && *connected)
infof(data, "Connected the data stream with PASV!\n");
}
return result;
}
/***********************************************************************
*
* 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)
CURLcode retcode=CURLE_OK;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
char *slash_pos; /* position of the first '/' char in curpos */
char *cur_pos=conn->ppath; /* current position in ppath. point at the begin
of next path component */
int path_part=0;/* current path component */
/* the ftp struct is already inited in ftp_connect() */
conn->size = -1; /* make sure this is unknown at this point */
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
Curl_pgrsSetUploadSize(data, 0);
Curl_pgrsSetDownloadSize(data, 0);
/* fixed : initialize ftp->dirs[xxx] to NULL !
is done in Curl_ftp_connect() */
Daniel Stenberg
committed
/* parse the URL path into separate path components */
while((slash_pos=strchr(cur_pos, '/'))) {
/* 1 or 0 to indicate absolute directory */
bool absolute_dir = (cur_pos - conn->ppath > 0) && (path_part == 0);
/* seek out the next path component */
Daniel Stenberg
committed
if (slash_pos-cur_pos) {
/* we skip empty path components, like "x//y" since the FTP command CWD
requires a parameter and a non-existant parameter a) doesn't work on
many servers and b) has no effect on the others. */
ftp->dirs[path_part] = curl_unescape(cur_pos - absolute_dir,
slash_pos - cur_pos + absolute_dir);
Daniel Stenberg
committed
if (!ftp->dirs[path_part]) { /* run out of memory ... */
failf(data, "no memory");
freedirs(ftp);
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
}
}
else {
Daniel Stenberg
committed
cur_pos = slash_pos + 1; /* jump to the rest of the string */
continue;
}
if(!retcode) {
cur_pos = slash_pos + 1; /* jump to the rest of the string */
Daniel Stenberg
committed
if(++path_part >= (CURL_MAX_FTP_DIRDEPTH-1)) {
/* too deep, we need the last entry to be kept NULL at all
times to signal end of list */
failf(data, "too deep dir hierarchy");
freedirs(ftp);
return CURLE_URL_MALFORMAT;
}
}
ftp->file = cur_pos; /* the rest is the file name */
if(*ftp->file) {
ftp->file = curl_unescape(ftp->file, 0);
if(NULL == ftp->file) {
freedirs(ftp);
failf(data, "no memory");
return CURLE_OUT_OF_MEMORY;
}
ftp->file=NULL; /* instead of point to a zero byte, we make it a NULL
pointer */
retcode = ftp_perform(conn, &connected);
Daniel Stenberg
committed
if(CURLE_OK == retcode) {
if(connected)
retcode = Curl_ftp_nextconnect(conn);
Daniel Stenberg
committed
if(retcode && (conn->sock[SECONDARYSOCKET] >= 0)) {
/* Failure detected, close the second socket if it was created already */
Daniel Stenberg
committed
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = -1;
if(ftp->no_transfer)
/* no data to transfer */
retcode=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
else if(!connected)
/* since we didn't connect now, we want do_more to get called */
conn->bits.do_more = TRUE;
}
else
freedirs(ftp);
Daniel Stenberg
committed
/***********************************************************************
*
* Curl_ftpsendf()
*
* Sends the formated string as a ftp command to a ftp server
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
*/
Daniel Stenberg
committed
CURLcode Curl_ftpsendf(struct connectdata *conn,
const char *fmt, ...)
ssize_t bytes_written;
char s[256];
ssize_t write_len;
char *sptr=s;
CURLcode res = CURLE_OK;
va_list ap;
va_start(ap, fmt);
vsnprintf(s, 250, fmt, ap);
va_end(ap);
strcat(s, "\r\n"); /* append a trailing CRLF */
Daniel Stenberg
committed
write_len = strlen(s);
do {
Daniel Stenberg
committed
res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
&bytes_written);
if(CURLE_OK != res)
break;
if(conn->data->set.verbose)
Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written);
if(bytes_written != write_len) {
write_len -= bytes_written;
sptr += bytes_written;
}
else
break;
} while(1);
return res;
}
/***********************************************************************
*
* Curl_ftp_disconnect()
*
* Disconnect from an FTP server. Cleanup protocol-specific per-connection
* resources
*/
CURLcode Curl_ftp_disconnect(struct connectdata *conn)
{
struct FTP *ftp= conn->proto.ftp;
Daniel Stenberg
committed
/* The FTP session may or may not have been allocated/setup at this point! */
if(ftp) {
if(ftp->entrypath)
free(ftp->entrypath);
if(ftp->cache) {
free(ftp->cache);
ftp->cache = NULL;
}
if(ftp->file) {
free(ftp->file);
ftp->file = NULL; /* zero */
}
freedirs(ftp);
Daniel Stenberg
committed
}
Sterling Hughes
committed
/***********************************************************************
*
* ftp_mkd()
*
* Makes a directory on the FTP server.
*/
CURLcode ftp_mkd(struct connectdata *conn, char *path)
{
CURLcode result=CURLE_OK;
int ftpcode; /* for ftp status */
ssize_t nread;
/* Create a directory on the remote server */
FTPSENDF(conn, "MKD %s", path);
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
if(result)
return result;
switch(ftpcode) {
case 257:
/* success! */
infof( conn->data , "Created remote directory %s\n" , path );
break;
case 550:
failf(conn->data, "Permission denied to make directory %s", path);
result = CURLE_FTP_ACCESS_DENIED;
break;
default:
failf(conn->data, "unrecognized MKD response: %d", ftpcode );
result = CURLE_FTP_ACCESS_DENIED;
break;
}
return result;
}
/***********************************************************************
*
* ftp_cwd()
*
* Send 'CWD' to the remote server to Change Working Directory. It is the ftp
* version of the unix 'cd' command. This function is only called from the
* ftp_cwd_and_mkd() function these days.
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
*
* This function does NOT call failf().
*/
static
CURLcode ftp_cwd(struct connectdata *conn, char *path)
{
ssize_t nread;
int ftpcode;
CURLcode result;
FTPSENDF(conn, "CWD %s", path);
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
if (!result) {
/* According to RFC959, CWD is supposed to return 250 on success, but
there seem to be non-compliant FTP servers out there that return 200,
so we accept any '2xy' code here. */
if (ftpcode/100 != 2)
result = CURLE_FTP_ACCESS_DENIED;
}
return result;
}
/***********************************************************************
*
* ftp_cwd_and_mkd()
*
* Change to the given directory. If the directory is not present, and we
* have been told to allow it, then create the directory and cd to it.
*/
static CURLcode ftp_cwd_and_mkd(struct connectdata *conn, char *path)
{
CURLcode result;
result = ftp_cwd(conn, path);
if (result) {
if(conn->data->set.ftp_create_missing_dirs) {
result = ftp_mkd(conn, path);
if (result)
/* ftp_mkd() calls failf() itself */
return result;
result = ftp_cwd(conn, path);
}
if(result)
failf(conn->data, "Couldn't cd to %s", path);
}
return result;
}
#endif /* CURL_DISABLE_FTP */