]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
ftp: fix 0-length last write on upload from stdin
authorStefan Eissing <stefan@eissing.org>
Tue, 1 Oct 2024 09:59:37 +0000 (11:59 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 1 Oct 2024 11:57:12 +0000 (13:57 +0200)
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

lib/vtls/vtls.c
tests/http/test_31_vsftpds.py
tests/http/testenv/curl.py

index 6c4ad33c0d045384bc5b5cfb14b4918005f830e8..52e43fdc02c537ca7f7990ee962ea369de19febc 100644 (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;
 }
 
index 95f3957cad6093906a3e71b74460a243c2b0739d..c723d148d5b3af46ae3bfd3d39ae7e2b99d8d116 100644 (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):
index 65caeb784c37c868ff13e63bf3a6eacaec958b0a..c7be99d9673b5b7533ec72c024c71a824ca92f67 100644 (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)