diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ad233f0..d2ab2c8b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "-a", "ed:wark", "-v", - "srv::r:aed" + "srv::r:aed:cnodupe" ] }, { diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index e7aa00bb..6fc93bcc 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -12,11 +12,12 @@ from .util import undot, Pebkac, fsdec, fsenc class VFS(object): """single level in the virtual fs""" - def __init__(self, realpath, vpath, uread=[], uwrite=[]): + def __init__(self, realpath, vpath, uread=[], uwrite=[], flags={}): self.realpath = realpath # absolute path on host filesystem self.vpath = vpath # absolute path in the virtual filesystem self.uread = uread # users who can read this self.uwrite = uwrite # users who can write this + self.flags = flags # config switches self.nodes = {} # child nodes def add(self, src, dst): @@ -36,6 +37,7 @@ class VFS(object): "{}/{}".format(self.vpath, name).lstrip("/"), self.uread, self.uwrite, + self.flags, ) self.nodes[name] = vn return vn.add(src, dst) @@ -161,7 +163,7 @@ class AuthSrv(object): yield prev, True - def _parse_config_file(self, fd, user, mread, mwrite, mount): + def _parse_config_file(self, fd, user, mread, mwrite, mflags, mount): vol_src = None vol_dst = None for ln in [x.decode("utf-8").strip() for x in fd]: @@ -191,6 +193,7 @@ class AuthSrv(object): mount[vol_dst] = vol_src mread[vol_dst] = [] mwrite[vol_dst] = [] + mflags[vol_dst] = {} continue lvl, uname = ln.split(" ") @@ -198,6 +201,9 @@ class AuthSrv(object): mread[vol_dst].append(uname) if lvl in "wa": mwrite[vol_dst].append(uname) + if lvl == "c": + # config option, currently switches only + mflags[vol_dst][uname] = True def reload(self): """ @@ -210,6 +216,7 @@ class AuthSrv(object): user = {} # username:password mread = {} # mountpoint:[username] mwrite = {} # mountpoint:[username] + mflags = {} # mountpoint:[flag] mount = {} # dst:src (mountpoint:realpath) if self.args.a: @@ -232,9 +239,13 @@ class AuthSrv(object): mount[dst] = src mread[dst] = [] mwrite[dst] = [] + mflags[dst] = {} perms = perms.split(":") for (lvl, uname) in [[x[0], x[1:]] for x in perms]: + if lvl == "c": + # config option, currently switches only + mflags[dst][uname] = True if uname == "": uname = "*" if lvl in "ra": @@ -245,14 +256,14 @@ class AuthSrv(object): if self.args.c: for cfg_fn in self.args.c: with open(cfg_fn, "rb") as f: - self._parse_config_file(f, user, mread, mwrite, mount) + self._parse_config_file(f, user, mread, mwrite, mflags, mount) if not mount: # -h says our defaults are CWD at root and read/write for everyone vfs = VFS(os.path.abspath("."), "", ["*"], ["*"]) elif "" not in mount: # there's volumes but no root; make root inaccessible - vfs = VFS(os.path.abspath("."), "", [], []) + vfs = VFS(os.path.abspath("."), "") maxdepth = 0 for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))): @@ -262,12 +273,13 @@ class AuthSrv(object): if dst == "": # rootfs was mapped; fully replaces the default CWD vfs - vfs = VFS(mount[dst], dst, mread[dst], mwrite[dst]) + vfs = VFS(mount[dst], dst, mread[dst], mwrite[dst], mflags[dst]) continue v = vfs.add(mount[dst], dst) v.uread = mread[dst] v.uwrite = mwrite[dst] + v.flags = mflags[dst] missing_users = {} for d in [mread, mwrite]: diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 263fe6cf..d208a04f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -387,6 +387,7 @@ class HttpCli(object): body["vdir"] = self.vpath body["rdir"] = os.path.join(vfs.realpath, rem) body["addr"] = self.addr[0] + body["flag"] = vfs.flags x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) response = x.get() diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 7a5932b3..1a56d6df 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -64,6 +64,12 @@ class Up2k(object): job["vdir"], job["name"] ) raise Pebkac(400, err) + elif "nodupe" in job["flag"]: + self.log("up2k", "dupe-reject:\n {0}\n {1}".format(src, dst)) + err = "upload rejected, file already exists:\n{0}{1} ".format( + job["vdir"], job["name"] + ) + raise Pebkac(400, err) else: # symlink to the client-provided name, # returning the previous upload info @@ -80,6 +86,7 @@ class Up2k(object): "addr": cj["addr"], "vdir": cj["vdir"], "rdir": cj["rdir"], + "flag": cj["flag"], # client-provided, sanitized by _get_wark: "name": cj["name"], "size": cj["size"], diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 9be7c7b0..480c0c25 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -670,7 +670,8 @@ function up2k_init(have_crypto) { else { var err = ""; var rsp = (xhr.responseText + ''); - if (rsp.indexOf('partial upload exists') !== -1) { + if (rsp.indexOf('partial upload exists') !== -1 || + rsp.indexOf('file already exists') !== -1) { err = rsp.slice(5); } if (err != "") {