ftp: simplify

- Avoid checking what's always true. The ftpcode pointer is always
  passed in, so use it.
- Simplified an indent level somewhat
- Split out two functions from the state machine

Closes #18403
This commit is contained in:
Daniel Stenberg 2025-08-26 16:03:24 +02:00
parent 98c78b2f14
commit 5beb644661
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

862
lib/ftp.c
View file

@ -571,12 +571,12 @@ static CURLcode ftp_readresp(struct Curl_easy *data,
struct ftp_conn *ftpc,
int sockindex,
struct pingpong *pp,
int *ftpcode, /* return the ftp-code if done */
int *ftpcodep, /* return the ftp-code if done */
size_t *size) /* size of the response */
{
int code;
CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size);
DEBUGASSERT(ftpcodep);
#ifdef HAVE_GSSAPI
{
struct connectdata *conn = data->conn;
@ -604,8 +604,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data,
if(!ftpc->shutdown)
data->info.httpcode = code;
if(ftpcode)
*ftpcode = code;
*ftpcodep = code;
if(code == 421) {
/* 421 means "Service not available, closing control connection." and FTP
@ -633,7 +632,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data,
CURLcode Curl_GetFTPResponse(struct Curl_easy *data,
ssize_t *nreadp, /* return number of bytes read */
int *ftpcode) /* return the ftp-code */
int *ftpcodep) /* return the ftp-code */
{
/*
* We cannot read just one byte per read() and then go back to select() as
@ -649,20 +648,16 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data,
struct pingpong *pp = &ftpc->pp;
size_t nread;
int cache_skip = 0;
int value_to_be_ignored = 0;
DEBUGASSERT(ftpcodep);
CURL_TRC_FTP(data, "getFTPResponse start");
*nreadp = 0;
if(ftpcode)
*ftpcode = 0; /* 0 for errors */
else
/* make the pointer point to something for the rest of this function */
ftpcode = &value_to_be_ignored;
*ftpcodep = 0; /* 0 for errors */
if(!ftpc)
return CURLE_FAILED_INIT;
while(!*ftpcode && !result) {
while(!*ftpcodep && !result) {
/* check and reset timeout value every lap */
timediff_t timeout = Curl_pp_state_timeout(data, pp, FALSE);
timediff_t interval_ms;
@ -720,7 +715,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data,
break;
}
result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcode, &nread);
result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcodep, &nread);
if(result)
break;
@ -739,7 +734,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data,
pp->pending_resp = FALSE;
CURL_TRC_FTP(data, "getFTPResponse -> result=%d, nread=%zd, ftpcode=%d",
result, *nreadp, *ftpcode);
result, *nreadp, *ftpcodep);
return result;
}
@ -2696,6 +2691,175 @@ static CURLcode ftp_state_acct_resp(struct Curl_easy *data,
return result;
}
static CURLcode ftp_pwd_resp(struct Curl_easy *data,
struct ftp_conn *ftpc,
int ftpcode)
{
struct pingpong *pp = &ftpc->pp;
CURLcode result;
if(ftpcode == 257) {
char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first
letter */
bool entry_extracted = FALSE;
struct dynbuf out;
curlx_dyn_init(&out, 1000);
/* Reply format is like
257<space>[rubbish]"<directory-name>"<space><commentary> and the
RFC959 says
The directory name can contain any character; embedded
double-quotes should be escaped by double-quotes (the
"quote-doubling" convention).
*/
/* scan for the first double-quote for non-standard responses */
while(*ptr != '\n' && *ptr != '\0' && *ptr != '"')
ptr++;
if('\"' == *ptr) {
/* it started good */
for(ptr++; *ptr; ptr++) {
if('\"' == *ptr) {
if('\"' == ptr[1]) {
/* "quote-doubling" */
result = curlx_dyn_addn(&out, &ptr[1], 1);
ptr++;
}
else {
/* end of path */
if(curlx_dyn_len(&out))
entry_extracted = TRUE;
break; /* get out of this loop */
}
}
else
result = curlx_dyn_addn(&out, ptr, 1);
if(result)
return result;
}
}
if(entry_extracted) {
/* If the path name does not look like an absolute path (i.e.: it
does not start with a '/'), we probably need some server-dependent
adjustments. For example, this is the case when connecting to
an OS400 FTP server: this server supports two name syntaxes,
the default one being incompatible with standard paths. In
addition, this server switches automatically to the regular path
syntax when one is encountered in a command: this results in
having an entrypath in the wrong syntax when later used in CWD.
The method used here is to check the server OS: we do it only
if the path name looks strange to minimize overhead on other
systems. */
char *dir = curlx_dyn_ptr(&out);
if(!ftpc->server_os && dir[0] != '/') {
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
if(result) {
free(dir);
return result;
}
free(ftpc->entrypath);
ftpc->entrypath = dir; /* remember this */
infof(data, "Entry path is '%s'", ftpc->entrypath);
/* also save it where getinfo can access it: */
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath);
if(!data->state.most_recent_ftp_entrypath)
return CURLE_OUT_OF_MEMORY;
ftp_state(data, ftpc, FTP_SYST);
return result;
}
free(ftpc->entrypath);
ftpc->entrypath = dir; /* remember this */
infof(data, "Entry path is '%s'", ftpc->entrypath);
/* also save it where getinfo can access it: */
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath);
if(!data->state.most_recent_ftp_entrypath)
return CURLE_OUT_OF_MEMORY;
}
else {
/* could not get the path */
curlx_dyn_free(&out);
infof(data, "Failed to figure out path");
}
}
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
return CURLE_OK;
}
static const char * const ftpauth[] = { "SSL", "TLS" };
static CURLcode ftp_wait_resp(struct Curl_easy *data,
struct connectdata *conn,
struct ftp_conn *ftpc,
int ftpcode)
{
CURLcode result = CURLE_OK;
if(ftpcode == 230) {
/* 230 User logged in - already! Take as 220 if TLS required. */
if(data->set.use_ssl <= CURLUSESSL_TRY ||
conn->bits.ftp_use_control_ssl)
return ftp_state_user_resp(data, ftpc, ftpcode);
}
else if(ftpcode != 220) {
failf(data, "Got a %03d ftp-server response when 220 was expected",
ftpcode);
return CURLE_WEIRD_SERVER_REPLY;
}
/* We have received a 220 response fine, now we proceed. */
#ifdef HAVE_GSSAPI
if(data->set.krb) {
/* If not anonymous login, try a secure login. Note that this
procedure is still BLOCKING. */
Curl_sec_request_prot(conn, "private");
/* We set private first as default, in case the line below fails to
set a valid level */
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
if(Curl_sec_login(data, conn)) {
failf(data, "secure login failed");
return CURLE_WEIRD_SERVER_REPLY;
}
infof(data, "Authentication successful");
}
#endif
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
/* We do not have an SSL/TLS control connection yet, but FTPS is
requested. Try an FTPS connection now */
ftpc->count3 = 0;
switch((long)data->set.ftpsslauth) {
case CURLFTPAUTH_DEFAULT:
case CURLFTPAUTH_SSL:
ftpc->count2 = 1; /* add one to get next */
ftpc->count1 = 0;
break;
case CURLFTPAUTH_TLS:
ftpc->count2 = -1; /* subtract one to get next */
ftpc->count1 = 1;
break;
default:
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
(int)data->set.ftpsslauth);
return CURLE_UNKNOWN_OPTION; /* we do not know what to do */
}
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
ftpauth[ftpc->count1]);
if(!result)
ftp_state(data, ftpc, FTP_AUTH);
}
else
result = ftp_state_user(data, ftpc, conn);
return result;
}
static CURLcode ftp_pp_statemachine(struct Curl_easy *data,
struct connectdata *conn)
@ -2705,7 +2869,6 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data,
struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN);
struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY);
struct pingpong *pp;
static const char * const ftpauth[] = { "SSL", "TLS" };
size_t nread = 0;
if(!ftpc || !ftp)
@ -2715,455 +2878,306 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data,
return Curl_pp_flushsend(data, pp);
result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, &ftpcode, &nread);
if(result)
if(result || !ftpcode)
return result;
if(ftpcode) {
/* we have now received a full FTP server response */
switch(ftpc->state) {
case FTP_WAIT220:
if(ftpcode == 230) {
/* 230 User logged in - already! Take as 220 if TLS required. */
if(data->set.use_ssl <= CURLUSESSL_TRY ||
conn->bits.ftp_use_control_ssl)
return ftp_state_user_resp(data, ftpc, ftpcode);
}
else if(ftpcode != 220) {
failf(data, "Got a %03d ftp-server response when 220 was expected",
ftpcode);
return CURLE_WEIRD_SERVER_REPLY;
}
/* we have now received a full FTP server response */
switch(ftpc->state) {
case FTP_WAIT220:
result = ftp_wait_resp(data, conn, ftpc, ftpcode);
break;
/* We have received a 220 response fine, now we proceed. */
#ifdef HAVE_GSSAPI
if(data->set.krb) {
/* If not anonymous login, try a secure login. Note that this
procedure is still BLOCKING. */
case FTP_AUTH:
/* we have gotten the response to a previous AUTH command */
Curl_sec_request_prot(conn, "private");
/* We set private first as default, in case the line below fails to
set a valid level */
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
if(pp->overflow)
return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */
if(Curl_sec_login(data, conn)) {
failf(data, "secure login failed");
return CURLE_WEIRD_SERVER_REPLY;
/* RFC2228 (page 5) says:
*
* If the server is willing to accept the named security mechanism,
* and does not require any security data, it must respond with
* reply code 234/334.
*/
if((ftpcode == 234) || (ftpcode == 334)) {
/* this was BLOCKING, keep it so for now */
bool done;
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result) {
/* we failed and bail out */
return CURLE_USE_SSL_FAILED;
}
infof(data, "Authentication successful");
}
#endif
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
/* We do not have an SSL/TLS control connection yet, but FTPS is
requested. Try an FTPS connection now */
ftpc->count3 = 0;
switch((long)data->set.ftpsslauth) {
case CURLFTPAUTH_DEFAULT:
case CURLFTPAUTH_SSL:
ftpc->count2 = 1; /* add one to get next */
ftpc->count1 = 0;
break;
case CURLFTPAUTH_TLS:
ftpc->count2 = -1; /* subtract one to get next */
ftpc->count1 = 1;
break;
default:
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
(int)data->set.ftpsslauth);
return CURLE_UNKNOWN_OPTION; /* we do not know what to do */
}
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
ftpauth[ftpc->count1]);
if(!result)
ftp_state(data, ftpc, FTP_AUTH);
}
else
result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done);
if(!result) {
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
result = ftp_state_user(data, ftpc, conn);
break;
case FTP_AUTH:
/* we have gotten the response to a previous AUTH command */
if(pp->overflow)
return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */
/* RFC2228 (page 5) says:
*
* If the server is willing to accept the named security mechanism,
* and does not require any security data, it must respond with
* reply code 234/334.
*/
if((ftpcode == 234) || (ftpcode == 334)) {
/* this was BLOCKING, keep it so for now */
bool done;
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result) {
/* we failed and bail out */
return CURLE_USE_SSL_FAILED;
}
}
result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done);
if(!result) {
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
result = ftp_state_user(data, ftpc, conn);
}
}
else if(ftpc->count3 < 1) {
ftpc->count3++;
ftpc->count1 += ftpc->count2; /* get next attempt */
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
ftpauth[ftpc->count1]);
/* remain in this same state */
}
else {
if(data->set.use_ssl > CURLUSESSL_TRY)
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
result = CURLE_USE_SSL_FAILED;
else
/* ignore the failure and continue */
result = ftp_state_user(data, ftpc, conn);
}
break;
case FTP_USER:
case FTP_PASS:
result = ftp_state_user_resp(data, ftpc, ftpcode);
break;
case FTP_ACCT:
result = ftp_state_acct_resp(data, ftpc, ftpcode);
break;
case FTP_PBSZ:
result =
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
if(!result)
ftp_state(data, ftpc, FTP_PROT);
break;
case FTP_PROT:
if(ftpcode/100 == 2)
/* We have enabled SSL for the data connection! */
conn->bits.ftp_use_data_ssl =
(data->set.use_ssl != CURLUSESSL_CONTROL);
/* FTP servers typically responds with 500 if they decide to reject
our 'P' request */
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
/* we failed and bails out */
return CURLE_USE_SSL_FAILED;
if(data->set.ftp_ccc) {
/* CCC - Clear Command Channel
*/
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
if(!result)
ftp_state(data, ftpc, FTP_CCC);
}
}
else if(ftpc->count3 < 1) {
ftpc->count3++;
ftpc->count1 += ftpc->count2; /* get next attempt */
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
ftpauth[ftpc->count1]);
/* remain in this same state */
}
else {
if(data->set.use_ssl > CURLUSESSL_TRY)
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
result = CURLE_USE_SSL_FAILED;
else
result = ftp_state_pwd(data, ftpc);
break;
/* ignore the failure and continue */
result = ftp_state_user(data, ftpc, conn);
}
break;
case FTP_CCC:
if(ftpcode < 500) {
/* First shut down the SSL layer (note: this call will block) */
/* This has only been tested on the proftpd server, and the mod_tls
* code sends a close notify alert without waiting for a close notify
* alert in response. Thus we wait for a close notify alert from the
* server, but we do not send one. Let's hope other servers do
* the same... */
result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET,
(data->set.ftp_ccc == (unsigned char)CURLFTPSSL_CCC_ACTIVE));
case FTP_USER:
case FTP_PASS:
result = ftp_state_user_resp(data, ftpc, ftpcode);
break;
if(result)
failf(data, "Failed to clear the command channel (CCC)");
}
case FTP_ACCT:
result = ftp_state_acct_resp(data, ftpc, ftpcode);
break;
case FTP_PBSZ:
result =
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
if(!result)
ftp_state(data, ftpc, FTP_PROT);
break;
case FTP_PROT:
if(ftpcode/100 == 2)
/* We have enabled SSL for the data connection! */
conn->bits.ftp_use_data_ssl =
(data->set.use_ssl != CURLUSESSL_CONTROL);
/* FTP servers typically responds with 500 if they decide to reject
our 'P' request */
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
/* we failed and bails out */
return CURLE_USE_SSL_FAILED;
if(data->set.ftp_ccc) {
/* CCC - Clear Command Channel
*/
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
if(!result)
/* Then continue as normal */
result = ftp_state_pwd(data, ftpc);
break;
ftp_state(data, ftpc, FTP_CCC);
}
else
result = ftp_state_pwd(data, ftpc);
break;
case FTP_PWD:
if(ftpcode == 257) {
char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first
letter */
bool entry_extracted = FALSE;
struct dynbuf out;
curlx_dyn_init(&out, 1000);
case FTP_CCC:
if(ftpcode < 500) {
/* First shut down the SSL layer (note: this call will block) */
/* This has only been tested on the proftpd server, and the mod_tls
* code sends a close notify alert without waiting for a close notify
* alert in response. Thus we wait for a close notify alert from the
* server, but we do not send one. Let's hope other servers do
* the same... */
result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET,
(data->set.ftp_ccc ==
(unsigned char)CURLFTPSSL_CCC_ACTIVE));
if(result)
failf(data, "Failed to clear the command channel (CCC)");
}
if(!result)
/* Then continue as normal */
result = ftp_state_pwd(data, ftpc);
break;
/* Reply format is like
257<space>[rubbish]"<directory-name>"<space><commentary> and the
RFC959 says
case FTP_PWD:
result = ftp_pwd_resp(data, ftpc, ftpcode);
break;
The directory name can contain any character; embedded
double-quotes should be escaped by double-quotes (the
"quote-doubling" convention).
*/
case FTP_SYST:
if(ftpcode == 215) {
char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first
letter */
char *os;
char *start;
/* scan for the first double-quote for non-standard responses */
while(*ptr != '\n' && *ptr != '\0' && *ptr != '"')
ptr++;
/* Reply format is like
215<space><OS-name><space><commentary>
*/
while(*ptr == ' ')
ptr++;
for(start = ptr; *ptr && *ptr != ' '; ptr++)
;
os = Curl_memdup0(start, ptr - start);
if(!os)
return CURLE_OUT_OF_MEMORY;
if('\"' == *ptr) {
/* it started good */
for(ptr++; *ptr; ptr++) {
if('\"' == *ptr) {
if('\"' == ptr[1]) {
/* "quote-doubling" */
result = curlx_dyn_addn(&out, &ptr[1], 1);
ptr++;
}
else {
/* end of path */
if(curlx_dyn_len(&out))
entry_extracted = TRUE;
break; /* get out of this loop */
}
}
else
result = curlx_dyn_addn(&out, ptr, 1);
if(result)
return result;
}
/* Check for special servers here. */
if(curl_strequal(os, "OS/400")) {
/* Force OS400 name format 1. */
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
if(result) {
free(os);
return result;
}
if(entry_extracted) {
/* If the path name does not look like an absolute path (i.e.: it
does not start with a '/'), we probably need some server-dependent
adjustments. For example, this is the case when connecting to
an OS400 FTP server: this server supports two name syntaxes,
the default one being incompatible with standard paths. In
addition, this server switches automatically to the regular path
syntax when one is encountered in a command: this results in
having an entrypath in the wrong syntax when later used in CWD.
The method used here is to check the server OS: we do it only
if the path name looks strange to minimize overhead on other
systems. */
char *dir = curlx_dyn_ptr(&out);
if(!ftpc->server_os && dir[0] != '/') {
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
if(result) {
free(dir);
return result;
}
free(ftpc->entrypath);
ftpc->entrypath = dir; /* remember this */
infof(data, "Entry path is '%s'", ftpc->entrypath);
/* also save it where getinfo can access it: */
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath);
if(!data->state.most_recent_ftp_entrypath)
return CURLE_OUT_OF_MEMORY;
ftp_state(data, ftpc, FTP_SYST);
break;
}
free(ftpc->entrypath);
ftpc->entrypath = dir; /* remember this */
infof(data, "Entry path is '%s'", ftpc->entrypath);
/* also save it where getinfo can access it: */
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath);
if(!data->state.most_recent_ftp_entrypath)
return CURLE_OUT_OF_MEMORY;
}
else {
/* could not get the path */
curlx_dyn_free(&out);
infof(data, "Failed to figure out path");
}
}
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
break;
case FTP_SYST:
if(ftpcode == 215) {
char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first
letter */
char *os;
char *start;
/* Reply format is like
215<space><OS-name><space><commentary>
*/
while(*ptr == ' ')
ptr++;
for(start = ptr; *ptr && *ptr != ' '; ptr++)
;
os = Curl_memdup0(start, ptr - start);
if(!os)
return CURLE_OUT_OF_MEMORY;
/* Check for special servers here. */
if(curl_strequal(os, "OS/400")) {
/* Force OS400 name format 1. */
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
if(result) {
free(os);
return result;
}
/* remember target server OS */
free(ftpc->server_os);
ftpc->server_os = os;
ftp_state(data, ftpc, FTP_NAMEFMT);
break;
}
/* Nothing special for the target server. */
/* remember target server OS */
free(ftpc->server_os);
ftpc->server_os = os;
}
else {
/* Cannot identify server OS. Continue anyway and cross fingers. */
}
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
break;
case FTP_NAMEFMT:
if(ftpcode == 250) {
/* Name format change successful: reload initial path. */
ftp_state_pwd(data, ftpc);
ftp_state(data, ftpc, FTP_NAMEFMT);
break;
}
/* Nothing special for the target server. */
/* remember target server OS */
free(ftpc->server_os);
ftpc->server_os = os;
}
else {
/* Cannot identify server OS. Continue anyway and cross fingers. */
}
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
break;
case FTP_NAMEFMT:
if(ftpcode == 250) {
/* Name format change successful: reload initial path. */
ftp_state_pwd(data, ftpc);
break;
}
case FTP_QUOTE:
case FTP_POSTQUOTE:
case FTP_RETR_PREQUOTE:
case FTP_STOR_PREQUOTE:
case FTP_LIST_PREQUOTE:
if((ftpcode >= 400) && !ftpc->count2) {
/* failure response code, and not allowed to fail */
failf(data, "QUOT command failed with %03d", ftpcode);
result = CURLE_QUOTE_ERROR;
}
else
result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state);
break;
ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */
CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc));
break;
case FTP_CWD:
if(ftpcode/100 != 2) {
/* failure to CWD there */
if(data->set.ftp_create_missing_dirs &&
ftpc->cwdcount && !ftpc->count2) {
/* try making it */
ftpc->count2++; /* counter to prevent CWD-MKD loops */
case FTP_QUOTE:
case FTP_POSTQUOTE:
case FTP_RETR_PREQUOTE:
case FTP_STOR_PREQUOTE:
case FTP_LIST_PREQUOTE:
if((ftpcode >= 400) && !ftpc->count2) {
/* failure response code, and not allowed to fail */
failf(data, "QUOT command failed with %03d", ftpcode);
result = CURLE_QUOTE_ERROR;
}
else
result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state);
break;
/* count3 is set to allow MKD to fail once per dir. In the case when
CWD fails and then MKD fails (due to another session raced it to
create the dir) this then allows for a second try to CWD to it. */
ftpc->count3 = (data->set.ftp_create_missing_dirs == 2) ? 1 : 0;
case FTP_CWD:
if(ftpcode/100 != 2) {
/* failure to CWD there */
if(data->set.ftp_create_missing_dirs &&
ftpc->cwdcount && !ftpc->count2) {
/* try making it */
ftpc->count2++; /* counter to prevent CWD-MKD loops */
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %.*s",
pathlen(ftpc, ftpc->cwdcount - 1),
pathpiece(ftpc, ftpc->cwdcount - 1));
if(!result)
ftp_state(data, ftpc, FTP_MKD);
}
else {
/* return failure */
failf(data, "Server denied you to change to the given directory");
ftpc->cwdfail = TRUE; /* do not remember this path as we failed
to enter it */
result = CURLE_REMOTE_ACCESS_DENIED;
}
/* count3 is set to allow MKD to fail once per dir. In the case when
CWD fails and then MKD fails (due to another session raced it to
create the dir) this then allows for a second try to CWD to it. */
ftpc->count3 = (data->set.ftp_create_missing_dirs == 2) ? 1 : 0;
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %.*s",
pathlen(ftpc, ftpc->cwdcount - 1),
pathpiece(ftpc, ftpc->cwdcount - 1));
if(!result)
ftp_state(data, ftpc, FTP_MKD);
}
else {
/* success */
ftpc->count2 = 0;
if(ftpc->cwdcount >= ftpc->dirdepth)
result = ftp_state_mdtm(data, ftpc, ftp);
else {
ftpc->cwdcount++;
/* send next CWD */
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s",
pathlen(ftpc, ftpc->cwdcount - 1),
pathpiece(ftpc, ftpc->cwdcount - 1));
}
}
break;
case FTP_MKD:
if((ftpcode/100 != 2) && !ftpc->count3--) {
/* failure to MKD the dir */
failf(data, "Failed to MKD dir: %03d", ftpcode);
/* return failure */
failf(data, "Server denied you to change to the given directory");
ftpc->cwdfail = TRUE; /* do not remember this path as we failed
to enter it */
result = CURLE_REMOTE_ACCESS_DENIED;
}
}
else {
/* success */
ftpc->count2 = 0;
if(ftpc->cwdcount >= ftpc->dirdepth)
result = ftp_state_mdtm(data, ftpc, ftp);
else {
ftp_state(data, ftpc, FTP_CWD);
/* send CWD */
ftpc->cwdcount++;
/* send next CWD */
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s",
pathlen(ftpc, ftpc->cwdcount - 1),
pathpiece(ftpc, ftpc->cwdcount - 1));
}
break;
case FTP_MDTM:
result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode);
break;
case FTP_TYPE:
case FTP_LIST_TYPE:
case FTP_RETR_TYPE:
case FTP_STOR_TYPE:
case FTP_RETR_LIST_TYPE:
result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_SIZE:
case FTP_RETR_SIZE:
case FTP_STOR_SIZE:
result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_REST:
case FTP_RETR_REST:
result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_PRET:
if(ftpcode != 200) {
/* there only is this one standard OK return code. */
failf(data, "PRET command not accepted: %03d", ftpcode);
return CURLE_FTP_PRET_FAILED;
}
result = ftp_state_use_pasv(data, ftpc, conn);
break;
case FTP_PASV:
result = ftp_state_pasv_resp(data, ftpc, ftpcode);
break;
case FTP_PORT:
result = ftp_state_port_resp(data, ftpc, ftp, ftpcode);
break;
case FTP_LIST:
case FTP_RETR:
result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_STOR:
result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state);
break;
case FTP_QUIT:
default:
/* internal error */
ftp_state(data, ftpc, FTP_STOP);
break;
}
} /* if(ftpcode) */
break;
case FTP_MKD:
if((ftpcode/100 != 2) && !ftpc->count3--) {
/* failure to MKD the dir */
failf(data, "Failed to MKD dir: %03d", ftpcode);
result = CURLE_REMOTE_ACCESS_DENIED;
}
else {
ftp_state(data, ftpc, FTP_CWD);
/* send CWD */
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s",
pathlen(ftpc, ftpc->cwdcount - 1),
pathpiece(ftpc, ftpc->cwdcount - 1));
}
break;
case FTP_MDTM:
result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode);
break;
case FTP_TYPE:
case FTP_LIST_TYPE:
case FTP_RETR_TYPE:
case FTP_STOR_TYPE:
case FTP_RETR_LIST_TYPE:
result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_SIZE:
case FTP_RETR_SIZE:
case FTP_STOR_SIZE:
result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_REST:
case FTP_RETR_REST:
result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_PRET:
if(ftpcode != 200) {
/* there only is this one standard OK return code. */
failf(data, "PRET command not accepted: %03d", ftpcode);
return CURLE_FTP_PRET_FAILED;
}
result = ftp_state_use_pasv(data, ftpc, conn);
break;
case FTP_PASV:
result = ftp_state_pasv_resp(data, ftpc, ftpcode);
break;
case FTP_PORT:
result = ftp_state_port_resp(data, ftpc, ftp, ftpcode);
break;
case FTP_LIST:
case FTP_RETR:
result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state);
break;
case FTP_STOR:
result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state);
break;
case FTP_QUIT:
default:
/* internal error */
ftp_state(data, ftpc, FTP_STOP);
break;
}
return result;
}