From c8f59fb978673630fcd3f59033715316e7afd463 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Mar 2021 00:20:42 +0100 Subject: [PATCH] up2k: add folder upload --- copyparty/authsrv.py | 1 + copyparty/httpcli.py | 31 ++++++++++----- copyparty/web/up2k.js | 82 +++++++++++++++++++++++++++++++++++---- copyparty/web/upload.css | 2 +- copyparty/web/upload.html | 3 +- 5 files changed, 100 insertions(+), 19 deletions(-) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index b0905c77..ea8b0658 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -54,6 +54,7 @@ class VFS(object): self.uwrite, self.flags, ) + self._trk(vn) self.nodes[name] = vn return self._trk(vn.add(src, dst)) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 373cb0ae..79675dd9 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -424,15 +424,18 @@ class HttpCli(object): if "srch" in self.uparam or "srch" in body: return self.handle_search(body) - # prefer this over undot; no reason to allow traversion - if "/" in body["name"]: - raise Pebkac(400, "folders verboten") - # up2k-php compat for k in "chunkpit.php", "handshake.php": if self.vpath.endswith(k): self.vpath = self.vpath[: -len(k)] + sub = None + name = undot(body["name"]) + if "/" in name: + sub, name = name.rsplit("/", 1) + self.vpath = "/".join([self.vpath, sub]).strip("/") + body["name"] = name + vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True) body["vtop"] = vfs.vpath @@ -441,12 +444,22 @@ class HttpCli(object): body["addr"] = self.ip body["vcfg"] = vfs.flags - x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) - response = x.get() - response = json.dumps(response) + if sub: + try: + dst = os.path.join(vfs.realpath, rem) + os.makedirs(dst) + except: + if not os.path.isdir(dst): + raise Pebkac(400, "some file got your folder name") - self.log(response) - self.reply(response.encode("utf-8"), mime="application/json") + x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) + ret = x.get() + if sub: + ret["name"] = "/".join([sub, ret["name"]]) + + ret = json.dumps(ret) + self.log(ret) + self.reply(ret.encode("utf-8"), mime="application/json") return True def handle_search(self, body): diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 7a7b5154..08df414b 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -284,12 +284,21 @@ function up2k_init(have_crypto) { more_one_file(); var bad_files = []; var good_files = []; + var dirs = []; for (var a = 0; a < files.length; a++) { var fobj = files[a]; if (is_itemlist) { if (fobj.kind !== 'file') continue; + try { + var wi = fobj.webkitGetAsEntry(); + if (wi.isDirectory) { + dirs.push(wi); + continue; + } + } + catch (ex) { } fobj = fobj.getAsFile(); } try { @@ -300,12 +309,69 @@ function up2k_init(have_crypto) { bad_files.push(fobj.name); continue; } - good_files.push(fobj); + good_files.push([fobj, fobj.name]); + } + if (dirs) { + return read_dirs(null, [], dirs, good_files, bad_files); + } + } + + function read_dirs(rd, pf, dirs, good, bad) { + if (!dirs.length) { + if (!pf.length) + return gotallfiles(good, bad); + + console.log("retry pf, " + pf.length); + setTimeout(function () { + read_dirs(rd, pf, dirs, good, bad); + }, 50); + return; } + if (!rd) + rd = dirs[0].createReader(); + + rd.readEntries(function (ents) { + var ngot = 0; + ents.forEach(function (dn) { + if (dn.isDirectory) { + dirs.push(dn); + } + else { + var name = dn.fullPath; + if (name.indexOf('/') === 0) + name = name.slice(1); + + pf.push(name); + dn.file(function (fobj) { + var idx = pf.indexOf(name); + pf.splice(idx, 1); + try { + if (fobj.size > 0) { + good.push([fobj, name]); + return; + } + } + catch (ex) { } + bad.push(name); + }); + } + ngot += 1; + }); + // console.log("ngot: " + ngot); + if (!ngot) { + dirs.shift(); + rd = null; + } + return read_dirs(rd, pf, dirs, good, bad); + }); + } + + function gotallfiles(good_files, bad_files) { if (bad_files.length > 0) { - var msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, files.length); - for (var a = 0; a < bad_files.length; a++) + var ntot = bad_files.length + good_files.length; + var msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot); + for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++) msg += '-- ' + bad_files[a] + '\n'; if (files.length - bad_files.length <= 1 && /(android)/i.test(navigator.userAgent)) @@ -315,21 +381,21 @@ function up2k_init(have_crypto) { } var msg = ['upload these ' + good_files.length + ' files?']; - for (var a = 0; a < good_files.length; a++) - msg.push(good_files[a].name); + for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++) + msg.push(good_files[a][1]); if (ask_up && !fsearch && !confirm(msg.join('\n'))) return; for (var a = 0; a < good_files.length; a++) { - var fobj = good_files[a]; + var fobj = good_files[a][0]; var now = new Date().getTime(); var lmod = fobj.lastModified || now; var entry = { "n": parseInt(st.files.length.toString()), - "t0": now, // TODO remove probably + "t0": now, "fobj": fobj, - "name": fobj.name, + "name": good_files[a][1], "size": fobj.size, "lmod": lmod / 1000, "purl": get_evpath(), diff --git a/copyparty/web/upload.css b/copyparty/web/upload.css index 32635e9b..d081fd0e 100644 --- a/copyparty/web/upload.css +++ b/copyparty/web/upload.css @@ -88,7 +88,7 @@ width: 30em; } #u2conf.has_btn { - width: 46em; + width: 48em; } #u2conf * { text-align: center; diff --git a/copyparty/web/upload.html b/copyparty/web/upload.html index 9095f8de..170f7c8a 100644 --- a/copyparty/web/upload.html +++ b/copyparty/web/upload.html @@ -73,7 +73,8 @@

- drop files here
+ drag/drop files
+ and folders here
(or click me)