ftp: fix prequotes for a directory in URL

Allow prequotes to be sent after curl has changed the working directory,
just before the listing command if the URL is a directory.

FTP state machine is updated with the new FTP_LIST_PREQUOTE state and
FTP_RETR_LIST_TYPE type.

Test 754 verifies

Fixes #8602
Closes #17616
This commit is contained in:
Bartosz Ruszczak 2025-03-14 21:53:42 +01:00 committed by Daniel Stenberg
parent 149d436457
commit fdf50d64b8
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
4 changed files with 96 additions and 4 deletions

View File

@ -114,12 +114,14 @@ static const char * const ftp_state_names[]={
"QUOTE",
"RETR_PREQUOTE",
"STOR_PREQUOTE",
"LIST_PREQUOTE",
"POSTQUOTE",
"CWD",
"MKD",
"MDTM",
"TYPE",
"LIST_TYPE",
"RETR_LIST_TYPE",
"RETR_TYPE",
"STOR_TYPE",
"SIZE",
@ -1459,6 +1461,14 @@ static CURLcode ftp_state_list(struct Curl_easy *data,
return result;
}
static CURLcode ftp_state_list_prequote(struct Curl_easy *data,
struct ftp_conn *ftpc,
struct FTP *ftp)
{
/* We have sent the TYPE, now we must send the list of prequote strings */
return ftp_state_quote(data, ftpc, ftp, TRUE, FTP_LIST_PREQUOTE);
}
static CURLcode ftp_state_retr_prequote(struct Curl_easy *data,
struct ftp_conn *ftpc,
struct FTP *ftp)
@ -1647,6 +1657,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data,
break;
case FTP_RETR_PREQUOTE:
case FTP_STOR_PREQUOTE:
case FTP_LIST_PREQUOTE:
item = data->set.prequote;
break;
case FTP_POSTQUOTE:
@ -1736,6 +1747,10 @@ static CURLcode ftp_state_quote(struct Curl_easy *data,
break;
case FTP_POSTQUOTE:
break;
case FTP_LIST_PREQUOTE:
ftp_state(data, ftpc, FTP_LIST_TYPE);
result = ftp_state_list(data, ftpc, ftp);
break;
}
}
@ -2209,6 +2224,8 @@ static CURLcode ftp_state_type_resp(struct Curl_easy *data,
result = ftp_state_retr_prequote(data, ftpc, ftp);
else if(instate == FTP_STOR_TYPE)
result = ftp_state_stor_prequote(data, ftpc, ftp);
else if(instate == FTP_RETR_LIST_TYPE)
result = ftp_state_list_prequote(data, ftpc, ftp);
return result;
}
@ -3013,6 +3030,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data,
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);
@ -3082,6 +3100,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data,
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;
@ -3686,7 +3705,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep)
if(result)
;
else if(data->state.list_only || !ftpc->file) {
else if((data->state.list_only || !ftpc->file) &&
!(data->set.prequote)) {
/* 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. */
@ -3700,8 +3720,14 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep)
/* otherwise just fall through */
}
else {
result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii,
FTP_RETR_TYPE);
if(data->set.prequote && !ftpc->file) {
result = ftp_nb_type(data, ftpc, ftp, TRUE,
FTP_RETR_LIST_TYPE);
}
else {
result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii,
FTP_RETR_TYPE);
}
if(result)
return result;
}

View File

@ -62,12 +62,14 @@ enum {
FTP_QUOTE, /* waiting for a response to a command sent in a quote list */
FTP_RETR_PREQUOTE,
FTP_STOR_PREQUOTE,
FTP_LIST_PREQUOTE,
FTP_POSTQUOTE,
FTP_CWD, /* change dir */
FTP_MKD, /* if the dir did not exist */
FTP_MDTM, /* to figure out the datestamp */
FTP_TYPE, /* to set type when doing a head-like request */
FTP_LIST_TYPE, /* set type when about to do a dir list */
FTP_RETR_LIST_TYPE,
FTP_RETR_TYPE, /* set type when about to RETR a file */
FTP_STOR_TYPE, /* set type when about to STOR a file */
FTP_SIZE, /* get the remote file's size for head-like request */

View File

@ -108,7 +108,7 @@ test718 test719 test720 test721 test722 test723 test724 test725 test726 \
test727 test728 test729 test730 test731 test732 test733 test734 test735 \
test736 test737 test738 test739 test740 test741 test742 test743 test744 \
test745 test746 test747 test748 test749 test750 test751 test752 test753 \
\
test754 \
test780 test781 test782 test783 test784 test785 test786 test787 test788 \
test789 test790 test791 \
\

64
tests/data/test754 Normal file
View File

@ -0,0 +1,64 @@
<testcase>
<info>
<keywords>
FTP
list
post-quote
pre-quote
</keywords>
</info>
# Server-side
<reply>
<data mode="text">
total 20
drwxr-xr-x 8 98 98 512 Oct 22 13:06 .
drwxr-xr-x 8 98 98 512 Oct 22 13:06 ..
drwxr-xr-x 2 98 98 512 May 2 1996 .NeXT
-r--r--r-- 1 0 1 35 Jul 16 1996 README
lrwxrwxrwx 1 0 1 7 Dec 9 1999 bin -> usr/bin
dr-xr-xr-x 2 0 1 512 Oct 1 1997 dev
drwxrwxrwx 2 98 98 512 May 29 16:04 download.html
dr-xr-xr-x 2 0 1 512 Nov 30 1995 etc
drwxrwxrwx 2 98 1 512 Oct 30 14:33 pub
dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr
</data>
<servercmd>
REPLY FAIL 500 this might not be a failure!
</servercmd>
</reply>
# Client-side
<client>
<server>
ftp
</server>
<name>
FTP list with quote ops
</name>
<command>
ftp://%HOSTIP:%FTPPORT/path/ -Q "NOOP 1" -Q "+NOOP 2" -Q "-NOOP 3" -Q "*FAIL" -Q "+*FAIL HARD"
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<strip>
QUIT
</strip>
<protocol>
USER anonymous
PASS ftp@example.com
PWD
NOOP 1
FAIL
CWD path
EPSV
TYPE A
NOOP 2
FAIL HARD
LIST
NOOP 3
QUIT
</protocol>
</verify>
</testcase>