mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
share multiple files (#84);
if files (one or more) are selected for sharing, then a virtual folder is created to hold the selected files if a single file is selected for sharing, then the returned URL will point directly to that file and fix some shares-related bugs: * password coalescing * log-spam on reload
This commit is contained in:
parent
55a77c5e89
commit
8122ddedfe
|
@ -750,7 +750,9 @@ you can move files across browser tabs (cut in one tab, paste in another)
|
||||||
|
|
||||||
share a file or folder by creating a temporary link
|
share a file or folder by creating a temporary link
|
||||||
|
|
||||||
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or select a file first to share only that file
|
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or alternatively:
|
||||||
|
* select a folder first to share that folder instead
|
||||||
|
* select one or more files to share only those files
|
||||||
|
|
||||||
this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
|
this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
|
||||||
|
|
||||||
|
@ -775,6 +777,8 @@ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named
|
||||||
|
|
||||||
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
|
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
|
||||||
|
|
||||||
|
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
|
||||||
|
|
||||||
|
|
||||||
## batch rename
|
## batch rename
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ from .util import (
|
||||||
odfusion,
|
odfusion,
|
||||||
relchk,
|
relchk,
|
||||||
statdir,
|
statdir,
|
||||||
|
ub64enc,
|
||||||
uncyg,
|
uncyg,
|
||||||
undot,
|
undot,
|
||||||
unhumanize,
|
unhumanize,
|
||||||
|
@ -344,6 +345,7 @@ class VFS(object):
|
||||||
self.dbv: Optional[VFS] = None # closest full/non-jump parent
|
self.dbv: Optional[VFS] = None # closest full/non-jump parent
|
||||||
self.lim: Optional[Lim] = None # upload limits; only set for dbv
|
self.lim: Optional[Lim] = None # upload limits; only set for dbv
|
||||||
self.shr_src: Optional[tuple[VFS, str]] = None # source vfs+rem of a share
|
self.shr_src: Optional[tuple[VFS, str]] = None # source vfs+rem of a share
|
||||||
|
self.shr_files: set[str] = set() # filenames to include from shr_src
|
||||||
self.aread: dict[str, list[str]] = {}
|
self.aread: dict[str, list[str]] = {}
|
||||||
self.awrite: dict[str, list[str]] = {}
|
self.awrite: dict[str, list[str]] = {}
|
||||||
self.amove: dict[str, list[str]] = {}
|
self.amove: dict[str, list[str]] = {}
|
||||||
|
@ -369,6 +371,7 @@ class VFS(object):
|
||||||
self.all_vps = []
|
self.all_vps = []
|
||||||
|
|
||||||
self.get_dbv = self._get_dbv
|
self.get_dbv = self._get_dbv
|
||||||
|
self.ls = self._ls
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "VFS(%s)" % (
|
return "VFS(%s)" % (
|
||||||
|
@ -565,7 +568,26 @@ class VFS(object):
|
||||||
ad, fn = os.path.split(ap)
|
ad, fn = os.path.split(ap)
|
||||||
return os.path.join(absreal(ad), fn)
|
return os.path.join(absreal(ad), fn)
|
||||||
|
|
||||||
def ls(
|
def _ls_nope(
|
||||||
|
self, *a, **ka
|
||||||
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
|
raise Pebkac(500, "nope.avi")
|
||||||
|
|
||||||
|
def _ls_shr(
|
||||||
|
self,
|
||||||
|
rem: str,
|
||||||
|
uname: str,
|
||||||
|
scandir: bool,
|
||||||
|
permsets: list[list[bool]],
|
||||||
|
lstat: bool = False,
|
||||||
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
|
"""replaces _ls for certain shares (single-file, or file selection)"""
|
||||||
|
vn, rem = self.shr_src # type: ignore
|
||||||
|
abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
|
||||||
|
real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
|
||||||
|
return abspath, real, {}
|
||||||
|
|
||||||
|
def _ls(
|
||||||
self,
|
self,
|
||||||
rem: str,
|
rem: str,
|
||||||
uname: str,
|
uname: str,
|
||||||
|
@ -1512,9 +1534,10 @@ class AuthSrv(object):
|
||||||
db_path = self.args.shr_db
|
db_path = self.args.shr_db
|
||||||
db = sqlite3.connect(db_path)
|
db = sqlite3.connect(db_path)
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
|
cur2 = db.cursor()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
for row in cur.execute("select * from sh"):
|
for row in cur.execute("select * from sh"):
|
||||||
s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
||||||
if s_t1 and s_t1 < now:
|
if s_t1 and s_t1 < now:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1523,7 +1546,10 @@ class AuthSrv(object):
|
||||||
self.log(t % (s_pr, s_k, s_un, s_vp))
|
self.log(t % (s_pr, s_k, s_un, s_vp))
|
||||||
|
|
||||||
if s_pw:
|
if s_pw:
|
||||||
sun = "s_%s" % (s_k,)
|
# gotta reuse the "account" for all shares with this pw,
|
||||||
|
# so do a light scramble as this appears in the web-ui
|
||||||
|
zs = ub64enc(hashlib.sha512(s_pw.encode("utf-8")).digest())[4:16]
|
||||||
|
sun = "s_%s" % (zs.decode("utf-8"),)
|
||||||
acct[sun] = s_pw
|
acct[sun] = s_pw
|
||||||
else:
|
else:
|
||||||
sun = "*"
|
sun = "*"
|
||||||
|
@ -1545,6 +1571,7 @@ class AuthSrv(object):
|
||||||
for vol in shv.nodes.values():
|
for vol in shv.nodes.values():
|
||||||
vfs.all_vols[vol.vpath] = vol
|
vfs.all_vols[vol.vpath] = vol
|
||||||
vol.get_dbv = vol._get_share_src
|
vol.get_dbv = vol._get_share_src
|
||||||
|
vol.ls = vol._ls_nope
|
||||||
|
|
||||||
zss = set(acct)
|
zss = set(acct)
|
||||||
zss.update(self.idp_accs)
|
zss.update(self.idp_accs)
|
||||||
|
@ -2054,6 +2081,9 @@ class AuthSrv(object):
|
||||||
if not self.warn_anonwrite or verbosity < 5:
|
if not self.warn_anonwrite or verbosity < 5:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if enshare and (zv.vpath == shr or zv.vpath.startswith(shrs)):
|
||||||
|
continue
|
||||||
|
|
||||||
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
||||||
for txt, attr in [
|
for txt, attr in [
|
||||||
[" read", "uread"],
|
[" read", "uread"],
|
||||||
|
@ -2160,10 +2190,9 @@ class AuthSrv(object):
|
||||||
if x != shr and not x.startswith(shrs)
|
if x != shr and not x.startswith(shrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert cur # type: ignore
|
assert db and cur and cur2 and shv # type: ignore
|
||||||
assert shv # type: ignore
|
|
||||||
for row in cur.execute("select * from sh"):
|
for row in cur.execute("select * from sh"):
|
||||||
s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
||||||
shn = shv.nodes.get(s_k, None)
|
shn = shv.nodes.get(s_k, None)
|
||||||
if not shn:
|
if not shn:
|
||||||
continue
|
continue
|
||||||
|
@ -2178,6 +2207,17 @@ class AuthSrv(object):
|
||||||
shv.nodes.pop(s_k)
|
shv.nodes.pop(s_k)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
fns = []
|
||||||
|
if s_nf:
|
||||||
|
q = "select vp from sf where k = ?"
|
||||||
|
for (s_fn,) in cur2.execute(q, (s_k,)):
|
||||||
|
fns.append(s_fn)
|
||||||
|
|
||||||
|
shn.shr_files = set(fns)
|
||||||
|
shn.ls = shn._ls_shr
|
||||||
|
else:
|
||||||
|
shn.ls = shn._ls
|
||||||
|
|
||||||
shn.shr_src = (s_vfs, s_rem)
|
shn.shr_src = (s_vfs, s_rem)
|
||||||
shn.realpath = s_vfs.canonical(s_rem)
|
shn.realpath = s_vfs.canonical(s_rem)
|
||||||
|
|
||||||
|
@ -2197,6 +2237,10 @@ class AuthSrv(object):
|
||||||
# hide subvolume
|
# hide subvolume
|
||||||
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
||||||
|
|
||||||
|
cur2.close()
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
def chpw(self, broker: Optional["BrokerCli"], uname, pw) -> tuple[bool, str]:
|
def chpw(self, broker: Optional["BrokerCli"], uname, pw) -> tuple[bool, str]:
|
||||||
if not self.args.chpw:
|
if not self.args.chpw:
|
||||||
return False, "feature disabled in server config"
|
return False, "feature disabled in server config"
|
||||||
|
|
|
@ -4347,11 +4347,31 @@ class HttpCli(object):
|
||||||
self.log("handle_share: " + json.dumps(req, indent=4))
|
self.log("handle_share: " + json.dumps(req, indent=4))
|
||||||
|
|
||||||
skey = req["k"]
|
skey = req["k"]
|
||||||
vp = req["vp"].strip("/")
|
vps = req["vp"]
|
||||||
|
fns = []
|
||||||
|
if len(vps) == 1:
|
||||||
|
vp = vps[0]
|
||||||
|
if not vp.endswith("/"):
|
||||||
|
vp, zs = vp.rsplit("/", 1)
|
||||||
|
fns = [zs]
|
||||||
|
else:
|
||||||
|
for zs in vps:
|
||||||
|
if zs.endswith("/"):
|
||||||
|
t = "you cannot select more than one folder, or mix flies and folders in one selection"
|
||||||
|
raise Pebkac(400, t)
|
||||||
|
vp = vps[0].rsplit("/", 1)[0]
|
||||||
|
for zs in vps:
|
||||||
|
vp2, fn = zs.rsplit("/", 1)
|
||||||
|
fns.append(fn)
|
||||||
|
if vp != vp2:
|
||||||
|
t = "mismatching base paths in selection:\n [%s]\n [%s]"
|
||||||
|
raise Pebkac(400, t % (vp, vp2))
|
||||||
|
|
||||||
|
vp = vp.strip("/")
|
||||||
if self.is_vproxied and (vp == self.args.R or vp.startswith(self.args.RS)):
|
if self.is_vproxied and (vp == self.args.R or vp.startswith(self.args.RS)):
|
||||||
vp = vp[len(self.args.RS) :]
|
vp = vp[len(self.args.RS) :]
|
||||||
|
|
||||||
m = re.search(r"([^0-9a-zA-Z_\.-]|\.\.|^\.)", skey)
|
m = re.search(r"([^0-9a-zA-Z_-])", skey)
|
||||||
if m:
|
if m:
|
||||||
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
|
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
|
||||||
|
|
||||||
|
@ -4378,9 +4398,13 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
||||||
|
|
||||||
ap = vfs.canonical(rem)
|
ap, reals, _ = vfs.ls(
|
||||||
st = bos.stat(ap)
|
rem, self.uname, not self.args.no_scandir, [[s_rd, s_wr, s_mv, s_del]]
|
||||||
ist = 2 if stat.S_ISDIR(st.st_mode) else 1
|
)
|
||||||
|
rfns = set([x[0] for x in reals])
|
||||||
|
for fn in fns:
|
||||||
|
if fn not in rfns:
|
||||||
|
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
||||||
|
|
||||||
pw = req.get("pw") or ""
|
pw = req.get("pw") or ""
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
@ -4390,18 +4414,25 @@ class HttpCli(object):
|
||||||
pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
|
pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
|
||||||
|
|
||||||
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
||||||
cur.execute(q, (skey, pw, vp, pr, ist, self.uname, now, exp))
|
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
||||||
cur.connection.commit()
|
|
||||||
|
|
||||||
|
q = "insert into sf values (?,?)"
|
||||||
|
for fn in fns:
|
||||||
|
cur.execute(q, (skey, fn))
|
||||||
|
|
||||||
|
cur.connection.commit()
|
||||||
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
||||||
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||||
|
|
||||||
surl = "%s://%s%s%s%s" % (
|
fn = quotep(fns[0]) if len(fns) == 1 else ""
|
||||||
|
|
||||||
|
surl = "created share: %s://%s%s%s%s/%s" % (
|
||||||
"https" if self.is_https else "http",
|
"https" if self.is_https else "http",
|
||||||
self.host,
|
self.host,
|
||||||
self.args.SR,
|
self.args.SR,
|
||||||
self.args.shr,
|
self.args.shr,
|
||||||
skey,
|
skey,
|
||||||
|
fn,
|
||||||
)
|
)
|
||||||
self.loud_reply(surl, status=201)
|
self.loud_reply(surl, status=201)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -377,7 +377,7 @@ class SvcHub(object):
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
al.shr = al.shr.strip("/")
|
al.shr = al.shr.strip("/")
|
||||||
if "/" in al.shr:
|
if "/" in al.shr or not al.shr:
|
||||||
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
|
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
|
||||||
self.log("root", t, 1)
|
self.log("root", t, 1)
|
||||||
raise Exception(t)
|
raise Exception(t)
|
||||||
|
@ -385,8 +385,9 @@ class SvcHub(object):
|
||||||
al.shr = "/%s/" % (al.shr,)
|
al.shr = "/%s/" % (al.shr,)
|
||||||
|
|
||||||
create = True
|
create = True
|
||||||
|
modified = False
|
||||||
db_path = self.args.shr_db
|
db_path = self.args.shr_db
|
||||||
self.log("root", "initializing shares-db %s" % (db_path,))
|
self.log("root", "opening shares-db %s" % (db_path,))
|
||||||
for n in range(2):
|
for n in range(2):
|
||||||
try:
|
try:
|
||||||
db = sqlite3.connect(db_path)
|
db = sqlite3.connect(db_path)
|
||||||
|
@ -412,18 +413,43 @@ class SvcHub(object):
|
||||||
pass
|
pass
|
||||||
os.unlink(db_path)
|
os.unlink(db_path)
|
||||||
|
|
||||||
|
sch1 = [
|
||||||
|
r"create table kv (k text, v int)",
|
||||||
|
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
||||||
|
# sharekey, password, src, perms, numFiles, owner, created, expires
|
||||||
|
]
|
||||||
|
sch2 = [
|
||||||
|
r"create table sf (k text, vp text)",
|
||||||
|
r"create index sf_k on sf(k)",
|
||||||
|
r"create index sh_k on sh(k)",
|
||||||
|
r"create index sh_t1 on sh(t1)",
|
||||||
|
]
|
||||||
|
|
||||||
assert db # type: ignore
|
assert db # type: ignore
|
||||||
assert cur # type: ignore
|
assert cur # type: ignore
|
||||||
if create:
|
if create:
|
||||||
|
dver = 2
|
||||||
|
modified = True
|
||||||
|
for cmd in sch1 + sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
self.log("root", "created new shares-db")
|
||||||
|
else:
|
||||||
|
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
||||||
|
|
||||||
|
if dver == 1:
|
||||||
|
modified = True
|
||||||
|
for cmd in sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
cur.execute("update sh set st = 0")
|
||||||
|
self.log("root", "shares-db schema upgrade ok")
|
||||||
|
|
||||||
|
if modified:
|
||||||
for cmd in [
|
for cmd in [
|
||||||
# sharekey, password, src, perms, type, owner, created, expires
|
r"delete from kv where k = 'sver'",
|
||||||
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
r"insert into kv values ('sver', %d)" % (2,),
|
||||||
r"create table kv (k text, v int)",
|
|
||||||
r"insert into kv values ('sver', {})".format(1),
|
|
||||||
]:
|
]:
|
||||||
cur.execute(cmd)
|
cur.execute(cmd)
|
||||||
db.commit()
|
db.commit()
|
||||||
self.log("root", "created new shares-db")
|
|
||||||
|
|
||||||
cur.close()
|
cur.close()
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -580,8 +580,8 @@ class Up2k(object):
|
||||||
rm = [x[0] for x in cur.execute(q, (now,))]
|
rm = [x[0] for x in cur.execute(q, (now,))]
|
||||||
if rm:
|
if rm:
|
||||||
self.log("forgetting expired shares %s" % (rm,))
|
self.log("forgetting expired shares %s" % (rm,))
|
||||||
q = "delete from sh where k=?"
|
cur.executemany("delete from sh where k=?", [(x,) for x in rm])
|
||||||
cur.executemany(q, [(x,) for x in rm])
|
cur.executemany("delete from sf where k=?", [(x,) for x in rm])
|
||||||
db.commit()
|
db.commit()
|
||||||
Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
|
Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
|
||||||
|
|
||||||
|
|
|
@ -310,8 +310,8 @@ var Ls = {
|
||||||
"fc_emore": "select at least one item to cut",
|
"fc_emore": "select at least one item to cut",
|
||||||
|
|
||||||
"fs_sc": "share the folder you're in",
|
"fs_sc": "share the folder you're in",
|
||||||
"fs_ss": "share the selected file/folder",
|
"fs_ss": "share the selected files",
|
||||||
"fs_just1": "select one or zero things to share",
|
"fs_just1d": "you cannot select more than one folder,\nor mix flies and folders in one selection",
|
||||||
"fs_abrt": "❌ abort",
|
"fs_abrt": "❌ abort",
|
||||||
"fs_rand": "🎲 rand.name",
|
"fs_rand": "🎲 rand.name",
|
||||||
"fs_go": "✅ create share",
|
"fs_go": "✅ create share",
|
||||||
|
@ -846,8 +846,8 @@ var Ls = {
|
||||||
"fc_emore": "velg minst én fil som skal klippes ut",
|
"fc_emore": "velg minst én fil som skal klippes ut",
|
||||||
|
|
||||||
"fs_sc": "del mappen du er i nå",
|
"fs_sc": "del mappen du er i nå",
|
||||||
"fs_ss": "del den valgte filen/mappen",
|
"fs_ss": "del de valgte filene",
|
||||||
"fs_just1": "velg 1 eller 0 ting å dele",
|
"fs_just1d": "du kan ikke markere flere mapper samtidig,\neller kombinere mapper og filer",
|
||||||
"fs_abrt": "❌ avbryt",
|
"fs_abrt": "❌ avbryt",
|
||||||
"fs_rand": "🎲 tilfeldig navn",
|
"fs_rand": "🎲 tilfeldig navn",
|
||||||
"fs_go": "✅ opprett deling",
|
"fs_go": "✅ opprett deling",
|
||||||
|
@ -1382,8 +1382,8 @@ var Ls = {
|
||||||
"fc_emore": "选择至少一个项目以剪切",
|
"fc_emore": "选择至少一个项目以剪切",
|
||||||
|
|
||||||
"fs_sc": "分享你所在的文件夹",
|
"fs_sc": "分享你所在的文件夹",
|
||||||
"fs_ss": "分享选定的文件/文件夹",
|
"fs_ss": "分享选定的文件",
|
||||||
"fs_just1": "选择一个或零个项目进行分享",
|
"fs_just1d": "你不能同时选择多个文件夹,也不能同时选择文件夹和文件",
|
||||||
"fs_abrt": "❌ 取消",
|
"fs_abrt": "❌ 取消",
|
||||||
"fs_rand": "🎲 随机名称",
|
"fs_rand": "🎲 随机名称",
|
||||||
"fs_go": "✅ 创建分享",
|
"fs_go": "✅ 创建分享",
|
||||||
|
@ -4286,7 +4286,6 @@ var fileman = (function () {
|
||||||
endel = nsel,
|
endel = nsel,
|
||||||
encut = nsel,
|
encut = nsel,
|
||||||
enpst = r.clip && r.clip.length,
|
enpst = r.clip && r.clip.length,
|
||||||
enshr = nsel < 2,
|
|
||||||
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
||||||
hdel = !(have_del && has(perms, 'delete')),
|
hdel = !(have_del && has(perms, 'delete')),
|
||||||
hcut = !(have_mv && has(perms, 'move')),
|
hcut = !(have_mv && has(perms, 'move')),
|
||||||
|
@ -4300,7 +4299,7 @@ var fileman = (function () {
|
||||||
clmod(bdel, 'en', endel);
|
clmod(bdel, 'en', endel);
|
||||||
clmod(bcut, 'en', encut);
|
clmod(bcut, 'en', encut);
|
||||||
clmod(bpst, 'en', enpst);
|
clmod(bpst, 'en', enpst);
|
||||||
clmod(bshr, 'en', enshr);
|
clmod(bshr, 'en', 1);
|
||||||
|
|
||||||
clmod(bren, 'hide', hren);
|
clmod(bren, 'hide', hren);
|
||||||
clmod(bdel, 'hide', hdel);
|
clmod(bdel, 'hide', hdel);
|
||||||
|
@ -4359,15 +4358,19 @@ var fileman = (function () {
|
||||||
r.share = function (e) {
|
r.share = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
|
||||||
var sel = msel.getsel();
|
var vp = uricom_dec(get_evpath()),
|
||||||
if (sel.length > 1)
|
sel = msel.getsel(),
|
||||||
return toast.err(3, L.fs_just1);
|
fns = [];
|
||||||
|
|
||||||
var vp = get_evpath();
|
for (var a = 0; a < sel.length; a++)
|
||||||
if (sel.length)
|
fns.push(uricom_dec(noq_href(ebi(sel[a].id))));
|
||||||
vp = sel[0].vp;
|
|
||||||
|
|
||||||
vp = uricom_dec(vp.split('?')[0]);
|
if (fns.length == 1 && fns[0].endsWith('/'))
|
||||||
|
vp = fns.pop();
|
||||||
|
|
||||||
|
for (var a = 0; a < fns.length; a++)
|
||||||
|
if (fns[a].endsWith('/'))
|
||||||
|
return toast.err(10, L.fs_just1d);
|
||||||
|
|
||||||
var shui = ebi('shui');
|
var shui = ebi('shui');
|
||||||
if (!shui) {
|
if (!shui) {
|
||||||
|
@ -4384,9 +4387,9 @@ var fileman = (function () {
|
||||||
'<button id="sh_rand">' + L.fs_rand + '</button>',
|
'<button id="sh_rand">' + L.fs_rand + '</button>',
|
||||||
'<button id="sh_apply">' + L.fs_go + '</button>',
|
'<button id="sh_apply">' + L.fs_go + '</button>',
|
||||||
'</td></tr>',
|
'</td></tr>',
|
||||||
'<tr><td>' + L.fs_name + '</td><td><input type="text" id="sh_k" ' + NOAC + ' placeholder="' + L.fs_pname + '" /></td></tr>',
|
'<tr><td>' + L.fs_name + '</td><td><input type="text" id="sh_k" ' + NOAC + ' placeholder=" ' + L.fs_pname + '" /></td></tr>',
|
||||||
'<tr><td>' + L.fs_src + '</td><td><input type="text" id="sh_vp" ' + NOAC + ' readonly tt="' + L.fs_tsrc + '" /></td></tr>',
|
'<tr><td>' + L.fs_src + '</td><td><input type="text" id="sh_vp" ' + NOAC + ' readonly tt="' + L.fs_tsrc + '" /></td></tr>',
|
||||||
'<tr><td>' + L.fs_pwd + '</td><td><input type="text" id="sh_pw" ' + NOAC + ' placeholder="' + L.fs_ppwd + '" /></td></tr>',
|
'<tr><td>' + L.fs_pwd + '</td><td><input type="text" id="sh_pw" ' + NOAC + ' placeholder=" ' + L.fs_ppwd + '" /></td></tr>',
|
||||||
'<tr><td>' + L.fs_exp + '</td><td class="exs">',
|
'<tr><td>' + L.fs_exp + '</td><td class="exs">',
|
||||||
'<input type="text" id="sh_exm" ' + NOAC + ' /> ' + L.fs_tmin + ' / ',
|
'<input type="text" id="sh_exm" ' + NOAC + ' /> ' + L.fs_tmin + ' / ',
|
||||||
'<input type="text" id="sh_exh" ' + NOAC + ' /> ' + L.fs_thrs + ' / ',
|
'<input type="text" id="sh_exh" ' + NOAC + ' /> ' + L.fs_thrs + ' / ',
|
||||||
|
@ -4452,10 +4455,7 @@ var fileman = (function () {
|
||||||
shui.parentNode.removeChild(shui);
|
shui.parentNode.removeChild(shui);
|
||||||
};
|
};
|
||||||
sh_rand.onclick = function () {
|
sh_rand.onclick = function () {
|
||||||
var v = randstr(12).replace(/l/g, 'n');
|
sh_k.value = randstr(12).replace(/l/g, 'n');
|
||||||
if (sel.length && !noq_href(ebi(sel[0].id)).endsWith('/'))
|
|
||||||
v += '.' + vp.split('.').pop();
|
|
||||||
sh_k.value = v;
|
|
||||||
};
|
};
|
||||||
tt.att(shui);
|
tt.att(shui);
|
||||||
|
|
||||||
|
@ -4468,11 +4468,17 @@ var fileman = (function () {
|
||||||
}
|
}
|
||||||
clmod(pbtns[0], 'on', 1);
|
clmod(pbtns[0], 'on', 1);
|
||||||
|
|
||||||
sh_vp.value = vp;
|
var vpt = vp;
|
||||||
|
if (fns.length) {
|
||||||
|
vpt = fns.length + ' files in ' + vp + ' '
|
||||||
|
for (var a = 0; a < fns.length; a++)
|
||||||
|
vpt += '「' + fns[a].split('/').pop() + '」';
|
||||||
|
}
|
||||||
|
sh_vp.value = vpt;
|
||||||
|
|
||||||
sh_k.oninput = function (e) {
|
sh_k.oninput = function (e) {
|
||||||
var v = this.value,
|
var v = this.value,
|
||||||
v2 = v.replace(/[^0-9a-zA-Z\.-]/g, '_');
|
v2 = v.replace(/[^0-9a-zA-Z-]/g, '_');
|
||||||
|
|
||||||
if (v != v2)
|
if (v != v2)
|
||||||
this.value = v2;
|
this.value = v2;
|
||||||
|
@ -4480,13 +4486,14 @@ var fileman = (function () {
|
||||||
|
|
||||||
function shr_cb() {
|
function shr_cb() {
|
||||||
toast.hide();
|
toast.hide();
|
||||||
if (this.status !== 201) {
|
var surl = this.responseText;
|
||||||
|
if (this.status !== 201 || !/^created share:/.exec(surl)) {
|
||||||
shui.style.display = 'block';
|
shui.style.display = 'block';
|
||||||
var msg = unpre(this.responseText);
|
var msg = unpre(surl);
|
||||||
toast.err(9, msg);
|
toast.err(9, msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var surl = this.responseText;
|
surl = surl.slice(15);
|
||||||
modal.confirm(L.fs_ok + esc(surl), function() {
|
modal.confirm(L.fs_ok + esc(surl), function() {
|
||||||
cliptxt(surl, function () {
|
cliptxt(surl, function () {
|
||||||
toast.ok(2, 'copied to clipboard');
|
toast.ok(2, 'copied to clipboard');
|
||||||
|
@ -4508,7 +4515,7 @@ var fileman = (function () {
|
||||||
|
|
||||||
var body = {
|
var body = {
|
||||||
"k": sh_k.value,
|
"k": sh_k.value,
|
||||||
"vp": sh_vp.value,
|
"vp": fns.length ? fns : [sh_vp.value],
|
||||||
"pw": sh_pw.value,
|
"pw": sh_pw.value,
|
||||||
"exp": exm.value,
|
"exp": exm.value,
|
||||||
"perms": plist,
|
"perms": plist,
|
||||||
|
@ -4519,6 +4526,8 @@ var fileman = (function () {
|
||||||
xhr.onload = xhr.onerror = shr_cb;
|
xhr.onload = xhr.onerror = shr_cb;
|
||||||
xhr.send(JSON.stringify(body));
|
xhr.send(JSON.stringify(body));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setTimeout(sh_pw.focus.bind(sh_pw), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
r.rename = function (e) {
|
r.rename = function (e) {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
|
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
|
||||||
|
|
||||||
<span>axs = perms (read,write,move,delet)</span>
|
<span>axs = perms (read,write,move,delet)</span>
|
||||||
<span>st 1=file 2=dir</span>
|
<span>nf = numFiles (0=dir)</span>
|
||||||
<span>min/hrs = time left</span>
|
<span>min/hrs = time left</span>
|
||||||
|
|
||||||
<table id="tab"><thead><tr>
|
<table id="tab"><thead><tr>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<th>pw</th>
|
<th>pw</th>
|
||||||
<th>source</th>
|
<th>source</th>
|
||||||
<th>axs</th>
|
<th>axs</th>
|
||||||
<th>st</th>
|
<th>nf</th>
|
||||||
<th>user</th>
|
<th>user</th>
|
||||||
<th>created</th>
|
<th>created</th>
|
||||||
<th>expires</th>
|
<th>expires</th>
|
||||||
|
|
Loading…
Reference in a new issue