shares: allow upload, unpost

* files can be uploaded into writeable shares

* add "write-only" button to the create-share ui

* unpost is possible while viewing the relevant share
This commit is contained in:
ed 2024-10-26 21:36:07 +00:00
parent 833c6cf2ec
commit 4bdcbc1cb5
6 changed files with 81 additions and 18 deletions

View file

@ -552,15 +552,14 @@ class VFS(object):
return self._get_dbv(vrem) return self._get_dbv(vrem)
shv, srem = src shv, srem = src
return shv, vjoin(srem, vrem) return shv._get_dbv(vjoin(srem, vrem))
def _get_dbv(self, vrem: str) -> tuple["VFS", str]: def _get_dbv(self, vrem: str) -> tuple["VFS", str]:
dbv = self.dbv dbv = self.dbv
if not dbv: if not dbv:
return self, vrem return self, vrem
tv = [self.vpath[len(dbv.vpath) :].lstrip("/"), vrem] vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem)
vrem = "/".join([x for x in tv if x])
return dbv, vrem return dbv, vrem
def canonical(self, rem: str, resolve: bool = True) -> str: def canonical(self, rem: str, resolve: bool = True) -> str:

View file

@ -105,6 +105,7 @@ from .util import (
unquotep, unquotep,
vjoin, vjoin,
vol_san, vol_san,
vroots,
vsplit, vsplit,
wrename, wrename,
wunlink, wunlink,
@ -1202,9 +1203,6 @@ class HttpCli(object):
if "stack" in self.uparam: if "stack" in self.uparam:
return self.tx_stack() return self.tx_stack()
if "ups" in self.uparam:
return self.tx_ups()
if "setck" in self.uparam: if "setck" in self.uparam:
return self.setck() return self.setck()
@ -1220,6 +1218,10 @@ class HttpCli(object):
if "h" in self.uparam: if "h" in self.uparam:
return self.tx_mounts() return self.tx_mounts()
if "ups" in self.uparam:
# vpath is used for share translation
return self.tx_ups()
if "rss" in self.uparam: if "rss" in self.uparam:
return self.tx_rss() return self.tx_rss()
@ -2408,6 +2410,15 @@ class HttpCli(object):
if "purl" in ret: if "purl" in ret:
ret["purl"] = self.args.SR + ret["purl"] ret["purl"] = self.args.SR + ret["purl"]
if self.args.shr and self.vpath.startswith(self.args.shr1):
# strip common suffix (uploader's folder structure)
vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
if not ret["purl"].startswith(vp_vfs):
t = "share-mapping failed; req=[%s] dbv=[%s] vrem=[%s] n1=[%s] n2=[%s] purl=[%s]"
zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"])
raise Pebkac(500, t % zt)
ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
ret = json.dumps(ret) ret = json.dumps(ret)
self.log(ret) self.log(ret)
self.reply(ret.encode("utf-8"), mime="application/json") self.reply(ret.encode("utf-8"), mime="application/json")
@ -2500,7 +2511,11 @@ class HttpCli(object):
chashes.append(siblings[n : n + clen]) chashes.append(siblings[n : n + clen])
vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True) vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
ptop = (vfs.dbv or vfs).realpath ptop = vfs.get_dbv("")[0].realpath
# if this is a share, then get_dbv has been overridden to return
# the dbv (which does not exist as a property). And its realpath
# could point into the middle of its origin vfs node, meaning it
# is not necessarily registered with up2k, so get_dbv is crucial
broker = self.conn.hsrv.broker broker = self.conn.hsrv.broker
x = broker.ask("up2k.handle_chunks", ptop, wark, chashes) x = broker.ask("up2k.handle_chunks", ptop, wark, chashes)
@ -4456,7 +4471,7 @@ class HttpCli(object):
rvol=rvol, rvol=rvol,
wvol=wvol, wvol=wvol,
avol=avol, avol=avol,
in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]), in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
vstate=vstate, vstate=vstate,
ups=ups, ups=ups,
scanning=vs["scanning"], scanning=vs["scanning"],
@ -4520,7 +4535,7 @@ class HttpCli(object):
t = t.format(self.args.SR) t = t.format(self.args.SR)
qv = quotep(self.vpaths) + self.ourlq() qv = quotep(self.vpaths) + self.ourlq()
in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:]) in_shr = self.args.shr and self.vpath.startswith(self.args.shr1)
html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t) html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
self.reply(html.encode("utf-8"), status=rc) self.reply(html.encode("utf-8"), status=rc)
return True return True
@ -4683,6 +4698,11 @@ class HttpCli(object):
lm = "ups [{}]".format(filt) lm = "ups [{}]".format(filt)
self.log(lm) self.log(lm)
if self.args.shr and self.vpath.startswith(self.args.shr1):
shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
else:
shr_dbv = None
ret: list[dict[str, Any]] = [] ret: list[dict[str, Any]] = []
t0 = time.time() t0 = time.time()
lim = time.time() - self.args.unpost lim = time.time() - self.args.unpost
@ -4703,7 +4723,12 @@ class HttpCli(object):
else: else:
allvols = list(self.asrv.vfs.all_vols.values()) allvols = list(self.asrv.vfs.all_vols.values())
allvols = [x for x in allvols if "e2d" in x.flags] allvols = [
x
for x in allvols
if "e2d" in x.flags
and ("*" in x.axs.uwrite or self.uname in x.axs.uwrite or x == shr_dbv)
]
for vol in allvols: for vol in allvols:
cur = idx.get_cur(vol) cur = idx.get_cur(vol)
@ -4753,6 +4778,16 @@ class HttpCli(object):
ret = ret[:2000] ret = ret[:2000]
if shr_dbv:
# translate vpaths from share-target to share-url
# to satisfy access checks
assert shr_vrem.split # type: ignore # !rm
vp_shr, vp_vfs = vroots(self.vpath, vjoin(shr_dbv.vpath, shr_vrem))
for v in ret:
vp = v["vp"]
if vp.startswith(vp_vfs):
v["vp"] = vp_shr + vp[len(vp_vfs) :]
if self.is_vproxied: if self.is_vproxied:
for v in ret: for v in ret:
v["vp"] = self.args.SR + v["vp"] v["vp"] = self.args.SR + v["vp"]
@ -4882,7 +4917,7 @@ class HttpCli(object):
if m: if m:
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],)) raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
if vp.startswith(self.args.shr[1:]): if vp.startswith(self.args.shr1):
raise Pebkac(400, "yo dawg...") raise Pebkac(400, "yo dawg...")
cur = idx.get_shr() cur = idx.get_shr()

View file

@ -230,6 +230,7 @@ class SvcHub(object):
if not self.args.no_ses: if not self.args.no_ses:
self.setup_session_db() self.setup_session_db()
args.shr1 = ""
if args.shr: if args.shr:
self.setup_share_db() self.setup_share_db()
@ -460,6 +461,7 @@ class SvcHub(object):
raise Exception(t) raise Exception(t)
al.shr = "/%s/" % (al.shr,) al.shr = "/%s/" % (al.shr,)
al.shr1 = al.shr[1:]
create = True create = True
modified = False modified = False

View file

@ -3907,11 +3907,9 @@ class Up2k(object):
if unpost: if unpost:
raise Pebkac(400, "cannot unpost folders") raise Pebkac(400, "cannot unpost folders")
elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode): elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
dbv, vrem = self.asrv.vfs.get(vpath, uname, *permsets[0]) voldir = vsplit(rem)[0]
dbv, vrem = dbv.get_dbv(vrem)
voldir = vsplit(vrem)[0]
vpath_dir = vsplit(vpath)[0] vpath_dir = vsplit(vpath)[0]
g = [(dbv, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore
else: else:
self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop)) self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop))
return 0, [], [] return 0, [], []
@ -3938,7 +3936,10 @@ class Up2k(object):
volpath = ("%s/%s" % (vrem, fn)).strip("/") volpath = ("%s/%s" % (vrem, fn)).strip("/")
vpath = ("%s/%s" % (dbv.vpath, volpath)).strip("/") vpath = ("%s/%s" % (dbv.vpath, volpath)).strip("/")
self.log("rm %s\n %s" % (vpath, abspath)) self.log("rm %s\n %s" % (vpath, abspath))
_ = dbv.get(volpath, uname, *permsets[0]) if not unpost:
# recursion-only sanchk
_ = dbv.get(volpath, uname, *permsets[0])
if xbd: if xbd:
if not runhook( if not runhook(
self.log, self.log,

View file

@ -2204,6 +2204,23 @@ def unquotep(txt: str) -> str:
return w8dec(unq2) return w8dec(unq2)
def vroots(vp1: str, vp2: str) -> tuple[str, str]:
"""
input("q/w/e/r","a/s/d/e/r") output("/q/w/","/a/s/d/")
"""
while vp1 and vp2:
zt1 = vp1.rsplit("/", 1) if "/" in vp1 else ("", vp1)
zt2 = vp2.rsplit("/", 1) if "/" in vp2 else ("", vp2)
if zt1[1] != zt2[1]:
break
vp1 = zt1[0]
vp2 = zt2[0]
return (
"/%s/" % (vp1,) if vp1 else "/",
"/%s/" % (vp2,) if vp2 else "/",
)
def vsplit(vpath: str) -> tuple[str, str]: def vsplit(vpath: str) -> tuple[str, str]:
if "/" not in vpath: if "/" not in vpath:
return "", vpath return "", vpath

View file

@ -4510,9 +4510,12 @@ var fileman = (function () {
'<tr><td>perms</td><td class="sh_axs">', '<tr><td>perms</td><td class="sh_axs">',
]; ];
for (var a = 0; a < perms.length; a++) for (var a = 0; a < perms.length; a++)
if (perms[a] != 'admin') if (!has(['admin', 'move'], perms[a]))
html.push('<a href="#" class="tgl btn">' + perms[a] + '</a>'); html.push('<a href="#" class="tgl btn">' + perms[a] + '</a>');
if (has(perms, 'write'))
html.push('<a href="#" class="btn">write-only</a>');
html.push('</td></tr></div'); html.push('</td></tr></div');
shui.innerHTML = html.join('\n'); shui.innerHTML = html.join('\n');
@ -4576,6 +4579,9 @@ var fileman = (function () {
function shspf() { function shspf() {
clmod(this, 'on', 't'); clmod(this, 'on', 't');
if (this.textContent == 'write-only')
for (var a = 0; a < pbtns.length; a++)
clmod(pbtns[a], 'on', pbtns[a].textContent == 'write');
} }
clmod(pbtns[0], 'on', 1); clmod(pbtns[0], 'on', 1);
@ -7380,6 +7386,9 @@ var treectl = (function () {
r.ls_cb = null; r.ls_cb = null;
fun(); fun();
} }
if (window.have_shr && QS('#op_unpost.act') && (cdir.startsWith(SR + have_shr) || get_evpath().startsWith(SR + have_shr)))
goto('unpost');
} }
r.chk_index_html = function (top, res) { r.chk_index_html = function (top, res) {
@ -9115,7 +9124,7 @@ var unpost = (function () {
r.me = me; r.me = me;
} }
var q = SR + '/?ups'; var q = get_evpath() + '?ups';
if (filt.value) if (filt.value)
q += '&filter=' + uricom_enc(filt.value, true); q += '&filter=' + uricom_enc(filt.value, true);