ftp: fix 0-length last write on upload from stdin

When uploading FTP with unknown length, we write a last 0-length chunk
with the EOS flag set. OpenSSL's SSL_write() errors on such a write.
Skip writing 0-length data to TLS backends instead.

Add test in FTPS for such uploads to verify.

Fixes #15101
Reported-by: Denis Goleshchikhin
Closes #15102
This commit is contained in:
Stefan Eissing 2024-10-01 11:59:37 +02:00 committed by Daniel Stenberg
parent 68c358619f
commit 72d2090fc2
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 44 additions and 11 deletions

View File

@ -1743,13 +1743,17 @@ static ssize_t ssl_cf_send(struct Curl_cfilter *cf,
bool eos, CURLcode *err)
{
struct cf_call_data save;
ssize_t nwritten;
ssize_t nwritten = 0;
(void)eos; /* unused */
CF_DATA_SAVE(save, cf, data);
(void)eos;
/* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */
*err = CURLE_OK;
nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
CF_DATA_RESTORE(cf, save);
if(len > 0) {
CF_DATA_SAVE(save, cf, data);
*err = CURLE_OK;
nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
CF_DATA_RESTORE(cf, save);
}
return nwritten;
}

View File

@ -220,6 +220,23 @@ class TestVsFTPD:
r.check_stats(count=count, http_status=226)
self.check_upload(env, vsftpds, docname=docname)
@pytest.mark.parametrize("indata", [
'1234567890', ''
])
def test_31_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata):
curl = CurlClient(env=env)
docname = "upload_31_10"
dstfile = os.path.join(vsftpds.docs_dir, docname)
self._rmf(dstfile)
count = 1
url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}'
r = curl.ftp_ssl_upload(urls=[url], updata=indata, with_stats=True)
r.check_stats(count=count, http_status=226)
assert os.path.exists(dstfile)
destdata = open(dstfile).readlines()
expdata = [indata] if len(indata) else []
assert expdata == destdata, f'exected: {expdata}, got: {destdata}'
def check_downloads(self, client, srcfile: str, count: int,
complete: bool = True):
for i in range(count):

View File

@ -696,27 +696,39 @@ class CurlClient:
with_tcpdump=with_tcpdump,
extra_args=extra_args)
def ftp_upload(self, urls: List[str], fupload,
def ftp_upload(self, urls: List[str],
fupload: Optional[Any] = None,
updata: Optional[str] = None,
with_stats: bool = True,
with_profile: bool = False,
with_tcpdump: bool = False,
extra_args: List[str] = None):
if extra_args is None:
extra_args = []
extra_args.extend([
'--upload-file', fupload
])
if fupload is not None:
extra_args.extend([
'--upload-file', fupload
])
elif updata is not None:
extra_args.extend([
'--upload-file', '-'
])
else:
raise Exception('need either file or data to upload')
if with_stats:
extra_args.extend([
'-w', '%{json}\\n'
])
return self._raw(urls, options=extra_args,
intext=updata,
with_stats=with_stats,
with_headers=False,
with_profile=with_profile,
with_tcpdump=with_tcpdump)
def ftp_ssl_upload(self, urls: List[str], fupload,
def ftp_ssl_upload(self, urls: List[str],
fupload: Optional[Any] = None,
updata: Optional[str] = None,
with_stats: bool = True,
with_profile: bool = False,
with_tcpdump: bool = False,
@ -726,7 +738,7 @@ class CurlClient:
extra_args.extend([
'--ssl-reqd',
])
return self.ftp_upload(urls=urls, fupload=fupload,
return self.ftp_upload(urls=urls, fupload=fupload, updata=updata,
with_stats=with_stats, with_profile=with_profile,
with_tcpdump=with_tcpdump,
extra_args=extra_args)