From 7ff46966dafb4901c001c19a6feb6b40190e15ec Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 19 Aug 2024 21:38:47 +0000 Subject: [PATCH] fix some issues with shares mentioned in #84; * crash when root volume is unmapped * rephrase login-page for shares * add chrome support (lol) * fix confusing helptext * improve ux * placeholders in share creator * button to disable expiration in share creator * human-readable timestamps in share listing --- README.md | 7 ++++-- copyparty/__main__.py | 4 ++-- copyparty/authsrv.py | 3 +-- copyparty/httpcli.py | 13 +++++++---- copyparty/svchub.py | 8 ++++++- copyparty/web/browser.js | 18 +++++++++++---- copyparty/web/shares.css | 3 +++ copyparty/web/shares.html | 12 +++++----- copyparty/web/shares.js | 18 +++++++++++++++ copyparty/web/splash.html | 48 +++++++++++++++++++++++++++------------ copyparty/web/splash.js | 5 ++-- 11 files changed, 99 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 68b03614..31f5172d 100644 --- a/README.md +++ b/README.md @@ -757,7 +757,7 @@ this feature was made with [identity providers](#identity-providers) in mind -- when creating a share, the creator can choose any of the following options: * password-protection -* expire after a certain time +* expire after a certain time; `0` or blank means infinite * allow visitors to upload (if the user who creates the share has write-access) semi-intentional limitations: @@ -768,7 +768,10 @@ semi-intentional limitations: * when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit * browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky) -the links are created inside a specific toplevel folder which must be specified with server-config `--shr`, for example `--shr /share/` (this also enables the feature) +specify `--shr /foobar` to enable this feature; a toplevel virtual folder named `foobar` is then created, and that's where all the shares will be served from + +* you can name it whatever, `foobar` is just an example +* if you're using config files, put `shr: /foobar` inside the `[global]` section instead users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 08e34367..c1533a30 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -975,8 +975,8 @@ def add_fs(ap): def add_share(ap): db_path = os.path.join(E.cfg, "shares.db") ap2 = ap.add_argument_group('share-url options') - ap2.add_argument("--shr", metavar="URL", default="", help="base url for shared files, for example [\033[32m/share\033[0m] (must be a toplevel subfolder)") - ap2.add_argument("--shr-db", metavar="PATH", default=db_path, help="database to store shares in") + ap2.add_argument("--shr", metavar="DIR", default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]") + ap2.add_argument("--shr-db", metavar="FILE", default=db_path, help="database to store shares in") ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share") ap2.add_argument("--shr-v", action="store_true", help="debug") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index d09f7089..592e7972 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1508,7 +1508,6 @@ class AuthSrv(object): import sqlite3 shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True}) - par = vfs.all_vols[""] db_path = self.args.shr_db db = sqlite3.connect(db_path) @@ -1539,7 +1538,7 @@ class AuthSrv(object): # don't know the abspath yet + wanna ensure the user # still has the privs they granted, so nullmap it shv.nodes[s_k] = VFS( - self.log_func, "", "%s/%s" % (shr, s_k), s_axs, par.flags.copy() + self.log_func, "", "%s/%s" % (shr, s_k), s_axs, shv.flags.copy() ) vfs.nodes[shr] = vfs.all_vols[shr] = shv diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index bd2c37fc..5432dcd3 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -3958,6 +3958,7 @@ class HttpCli(object): rvol=rvol, wvol=wvol, avol=avol, + in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]), vstate=vstate, scanning=vs["scanning"], hashq=vs["hashq"], @@ -4006,10 +4007,10 @@ class HttpCli(object): def tx_404(self, is_403: bool = False) -> bool: rc = 404 if self.args.vague_403: - t = '

404 not found  ┐( ´ -`)┌

or maybe you don\'t have access -- try logging in or go home

' - pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try logging in)" + t = '

404 not found  ┐( ´ -`)┌

or maybe you don\'t have access -- try a password or go home

' + pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try a password)" elif is_403: - t = '

403 forbiddena  ~┻━┻

you\'ll have to log in or go home

' + t = '

403 forbiddena  ~┻━┻

use a password or go home

' pt = "403 forbiddena ~┻━┻ (you'll have to log in)" rc = 403 else: @@ -4026,7 +4027,8 @@ class HttpCli(object): t = t.format(self.args.SR) qv = quotep(self.vpaths) + self.ourlq() - html = self.j2s("splash", this=self, qvpath=qv, msg=t) + in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:]) + html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t) self.reply(html.encode("utf-8"), status=rc) return True @@ -4382,7 +4384,8 @@ class HttpCli(object): pw = req.get("pw") or "" now = int(time.time()) sexp = req["exp"] - exp = now + int(sexp) * 60 if sexp else 0 + exp = int(sexp) if sexp else 0 + exp = now + exp * 60 if exp else 0 pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb) q = "insert into sh values (?,?,?,?,?,?,?,?)" diff --git a/copyparty/svchub.py b/copyparty/svchub.py index b67359e3..3d18eacf 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -376,7 +376,13 @@ class SvcHub(object): import sqlite3 - al.shr = "/%s/" % (al.shr.strip("/")) + al.shr = al.shr.strip("/") + if "/" in al.shr: + t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside" + self.log("root", t, 1) + raise Exception(t) + + al.shr = "/%s/" % (al.shr,) create = True db_path = self.args.shr_db diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index ac01fa72..81c8a806 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -3820,13 +3820,14 @@ var fileman = (function () { '', '', '', - 'name', + 'name', 'source', - 'passwd', + 'passwd', 'expiry', ' min / ', ' hours / ', - ' days', + ' days / ', + '', '', 'perms', ]; @@ -3840,11 +3841,12 @@ var fileman = (function () { var sh_rand = ebi('sh_rand'), sh_abrt = ebi('sh_abrt'), sh_apply = ebi('sh_apply'), + sh_noex = ebi('sh_noex'), exm = ebi('sh_exm'), exh = ebi('sh_exh'), exd = ebi('sh_exd'), sh_k = ebi('sh_k'), - sh_vp = ebi('sh_vp'); + sh_vp = ebi('sh_vp'), sh_pw = ebi('sh_pw'); function setexp(a, b) { @@ -3870,6 +3872,9 @@ var fileman = (function () { exm.onfocus = exh.onfocus = exd.onfocus = function () { this.value = ''; }; + sh_noex.onclick = function () { + setexp(0, 1); + }; exm.onblur = exh.onblur = exd.onblur = setdef; exm.onkeydown = exh.onkeydown = exd.onkeydown = @@ -3910,6 +3915,7 @@ var fileman = (function () { }; function shr_cb() { + toast.hide(); if (this.status !== 201) { shui.style.display = 'block'; var msg = unpre(this.responseText); @@ -3919,7 +3925,7 @@ var fileman = (function () { var surl = this.responseText; modal.confirm(L.fs_ok + esc(surl), function() { cliptxt(surl, function () { - toast.ok(1, 'copied to clipboard'); + toast.ok(2, 'copied to clipboard'); }); }); } @@ -3934,6 +3940,8 @@ var fileman = (function () { plist.push(pbtns[a].textContent); shui.style.display = 'none'; + toast.inf(30, "creating share..."); + var body = { "k": sh_k.value, "vp": sh_vp.value, diff --git a/copyparty/web/shares.css b/copyparty/web/shares.css index 77ce24d6..9c48ac0a 100644 --- a/copyparty/web/shares.css +++ b/copyparty/web/shares.css @@ -58,6 +58,9 @@ td, th { text-align: left; white-space: nowrap; } +td+td+td+td+td+td+td+td { + font-family: var(--font-mono), monospace, monospace; +} diff --git a/copyparty/web/shares.html b/copyparty/web/shares.html index 37f17649..028f99c6 100644 --- a/copyparty/web/shares.html +++ b/copyparty/web/shares.html @@ -15,13 +15,13 @@
refresh - controlpanel + control-panel axs = perms (read,write,move,delet) st 1=file 2=dir min/hrs = time left - +
@@ -33,7 +33,7 @@ - + {% for k, pw, vp, pr, st, un, t0, t1 in rows %} @@ -45,11 +45,11 @@ - - + + {% endfor %} -
delete sharekey pwexpires min hrs
delete{{ un|e }} {{ t0 }} {{ t1 }}{{ (t1 - now) // 60 if t1 else "never" }}{{ (t1 - now) // 3600 if t1 else "never" }}{{ ((t1 - now) / 60) | round(1) if t1 else "inf" }}{{ ((t1 - now) / 3600) | round(1) if t1 else "inf" }}
+ {% if not rows %} (you don't have any active shares btw) {% endif %} diff --git a/copyparty/web/shares.js b/copyparty/web/shares.js index 70139967..90b02b50 100644 --- a/copyparty/web/shares.js +++ b/copyparty/web/shares.js @@ -17,3 +17,21 @@ function cb() { document.location = '?shares'; } + +(function() { + var tab = ebi('tab').tBodies[0], + tr = Array.prototype.slice.call(tab.rows, 0); + + var buf = []; + for (var a = 0; a < tr.length; a++) + for (var b = 7; b < 9; b++) + buf.push(parseInt(tr[a].cells[b].innerHTML)); + + var ibuf = 0; + for (var a = 0; a < tr.length; a++) + for (var b = 7; b < 9; b++) { + var v = buf[ibuf++]; + tr[a].cells[b].innerHTML = + v ? unix2iso(v).replace(' ', ', ') : 'never'; + } +})(); diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index e979566a..de6dac6c 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -14,6 +14,7 @@
+ {%- if not in_shr %} refresh connect @@ -23,6 +24,7 @@ logout

welcome back, {{ this.uname|e }}

{%- endif %} + {%- endif %} {%- if msg %}
@@ -76,6 +78,37 @@ {%- endif %} + {%- if in_shr %} +

unlock this share:

+
+
+ + + + + {% if ahttps %} + switch to https + {% endif %} +
+
+ {%- else %} +

login for more:

+
+
+ + + + + {% if chpw %} + change password + {% endif %} + {% if ahttps %} + switch to https + {% endif %} +
+
+ {%- endif %} +

other stuff:

-

login for more:

-
-
- - - - - {% if chpw %} - change password - {% endif %} - {% if ahttps %} - switch to https - {% endif %} -
-
π {%- if not this.args.nb %} diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index 2c786086..1ad2366b 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -17,9 +17,9 @@ var Ls = { "l1": "logg inn:", "m1": "velkommen tilbake,", "n1": "404: filen finnes ikke  ┐( ´ -`)┌", - "o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller gå hjem', + "o1": 'eller kanskje du ikke har tilgang? prøv et passord eller gå hjem', "p1": "403: tilgang nektet  ~┻━┻", - "q1": 'du må logge inn eller gå hjem', + "q1": 'prøv et passord eller gå hjem', "r1": "gå hjem", ".s1": "kartlegg", "t1": "handling", @@ -29,6 +29,7 @@ var Ls = { "w1": "bytt til https", "x1": "bytt passord", "y1": "dine delinger", + "z1": "lås opp område", "ta1": "du må skrive et nytt passord først", "ta2": "gjenta for å bekrefte nytt passord:", "ta3": "fant en skrivefeil; vennligst prøv igjen",