From 0a9f4c6074a30e8ff497463062183615e898d4bf Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 18 Jun 2024 12:07:45 +0200 Subject: [PATCH] ftpd: allow implicit overwrite if user has delete perms the spec doesn't say what you're supposed to do if the target filename of an upload is already taken, but this seems to be the most common behavior on other ftp servers, and is required by wondows 2000 (otherwise it'll freak out and issue a delete and then not actually upload it, nice) new option `--ftp-no-ow` restores old default behavior of rejecting upload if target filename exists --- copyparty/__main__.py | 1 + copyparty/ftpd.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index d9dc64dc..3c7e46e3 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1034,6 +1034,7 @@ def add_ftp(ap): ap2.add_argument("--ftpv", action="store_true", help="verbose") ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite") ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections") ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example \033[32m12000-13000") diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index a918b234..ea6a9798 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING from .authsrv import VFS from .bos import bos from .util import ( + VF_CAREFUL, Daemon, ODict, Pebkac, @@ -30,6 +31,7 @@ from .util import ( runhook, sanitize_fn, vjoin, + wunlink, ) if TYPE_CHECKING: @@ -37,7 +39,7 @@ if TYPE_CHECKING: if True: # pylint: disable=using-constant-test import typing - from typing import Any, Optional + from typing import Any, Optional, Union class FSE(FilesystemError): @@ -139,6 +141,9 @@ class FtpFs(AbstractedFS): self.listdirinfo = self.listdir self.chdir(".") + def log(self, msg: str, c: Union[int, str] = 0) -> None: + self.hub.log("ftpd", msg, c) + def v2a( self, vpath: str, @@ -207,17 +212,37 @@ class FtpFs(AbstractedFS): w = "w" in mode or "a" in mode or "+" in mode ap = self.rv2a(filename, r, w)[0] + self.validpath(ap) if w: try: st = bos.stat(ap) td = time.time() - st.st_mtime + need_unlink = True except: + need_unlink = False td = 0 - if td < -1 or td > self.args.ftp_wt: - raise FSE("Cannot open existing file for writing") + if w and need_unlink: + if td >= -1 and td <= self.args.ftp_wt: + # within permitted timeframe; unlink and accept + do_it = True + elif self.args.no_del or self.args.ftp_no_ow: + # file too old, or overwrite not allowed; reject + do_it = False + else: + # allow overwrite if user has delete permission + # (avoids win2000 freaking out and deleting the server copy without uploading its own) + try: + self.rv2a(filename, False, True, False, True) + do_it = True + except: + do_it = False + + if not do_it: + raise FSE("File already exists") + + wunlink(self.log, ap, VF_CAREFUL) - self.validpath(ap) return open(fsenc(ap), mode, self.args.iobuf) def chdir(self, path: str) -> None: