copyparty 1.14.1__py3-none-any.whl → 1.14.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copyparty/__main__.py +5 -4
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +51 -8
- copyparty/httpcli.py +74 -21
- copyparty/svchub.py +40 -7
- copyparty/tftpd.py +2 -2
- copyparty/up2k.py +65 -12
- copyparty/web/a/u2c.py +5 -3
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/shares.css.gz +0 -0
- copyparty/web/shares.html +10 -8
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.css.gz +0 -0
- copyparty/web/splash.html +33 -15
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/METADATA +16 -5
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/RECORD +24 -24
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/WHEEL +1 -1
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/LICENSE +0 -0
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/entry_points.txt +0 -0
- {copyparty-1.14.1.dist-info → copyparty-1.14.3.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -969,9 +969,10 @@ def add_fs(ap):
|
|
969
969
|
def add_share(ap):
|
970
970
|
db_path = os.path.join(E.cfg, "shares.db")
|
971
971
|
ap2 = ap.add_argument_group('share-url options')
|
972
|
-
ap2.add_argument("--shr", metavar="
|
973
|
-
ap2.add_argument("--shr-db", metavar="
|
974
|
-
ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share")
|
972
|
+
ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
|
973
|
+
ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in")
|
974
|
+
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
|
975
|
+
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
|
975
976
|
ap2.add_argument("--shr-v", action="store_true", help="debug")
|
976
977
|
|
977
978
|
|
@@ -1406,7 +1407,7 @@ def add_ui(ap, retry):
|
|
1406
1407
|
ap2 = ap.add_argument_group('ui options')
|
1407
1408
|
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
1408
1409
|
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
1409
|
-
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor\033[0m")
|
1410
|
+
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
|
1410
1411
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
|
1411
1412
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
1412
1413
|
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -35,6 +35,7 @@ from .util import (
|
|
35
35
|
odfusion,
|
36
36
|
relchk,
|
37
37
|
statdir,
|
38
|
+
ub64enc,
|
38
39
|
uncyg,
|
39
40
|
undot,
|
40
41
|
unhumanize,
|
@@ -337,6 +338,7 @@ class VFS(object):
|
|
337
338
|
self.dbv = None # closest full/non-jump parent
|
338
339
|
self.lim = None # upload limits; only set for dbv
|
339
340
|
self.shr_src = None # source vfs+rem of a share
|
341
|
+
self.shr_files = set() # filenames to include from shr_src
|
340
342
|
self.aread = {}
|
341
343
|
self.awrite = {}
|
342
344
|
self.amove = {}
|
@@ -362,6 +364,7 @@ class VFS(object):
|
|
362
364
|
self.all_vps = []
|
363
365
|
|
364
366
|
self.get_dbv = self._get_dbv
|
367
|
+
self.ls = self._ls
|
365
368
|
|
366
369
|
def __repr__(self) :
|
367
370
|
return "VFS(%s)" % (
|
@@ -558,7 +561,26 @@ class VFS(object):
|
|
558
561
|
ad, fn = os.path.split(ap)
|
559
562
|
return os.path.join(absreal(ad), fn)
|
560
563
|
|
561
|
-
def
|
564
|
+
def _ls_nope(
|
565
|
+
self, *a, **ka
|
566
|
+
) :
|
567
|
+
raise Pebkac(500, "nope.avi")
|
568
|
+
|
569
|
+
def _ls_shr(
|
570
|
+
self,
|
571
|
+
rem ,
|
572
|
+
uname ,
|
573
|
+
scandir ,
|
574
|
+
permsets ,
|
575
|
+
lstat = False,
|
576
|
+
) :
|
577
|
+
"""replaces _ls for certain shares (single-file, or file selection)"""
|
578
|
+
vn, rem = self.shr_src # type: ignore
|
579
|
+
abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
|
580
|
+
real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
|
581
|
+
return abspath, real, {}
|
582
|
+
|
583
|
+
def _ls(
|
562
584
|
self,
|
563
585
|
rem ,
|
564
586
|
uname ,
|
@@ -1501,14 +1523,14 @@ class AuthSrv(object):
|
|
1501
1523
|
import sqlite3
|
1502
1524
|
|
1503
1525
|
shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True})
|
1504
|
-
par = vfs.all_vols[""]
|
1505
1526
|
|
1506
1527
|
db_path = self.args.shr_db
|
1507
1528
|
db = sqlite3.connect(db_path)
|
1508
1529
|
cur = db.cursor()
|
1530
|
+
cur2 = db.cursor()
|
1509
1531
|
now = time.time()
|
1510
1532
|
for row in cur.execute("select * from sh"):
|
1511
|
-
s_k, s_pw, s_vp, s_pr,
|
1533
|
+
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
1512
1534
|
if s_t1 and s_t1 < now:
|
1513
1535
|
continue
|
1514
1536
|
|
@@ -1517,7 +1539,10 @@ class AuthSrv(object):
|
|
1517
1539
|
self.log(t % (s_pr, s_k, s_un, s_vp))
|
1518
1540
|
|
1519
1541
|
if s_pw:
|
1520
|
-
|
1542
|
+
# gotta reuse the "account" for all shares with this pw,
|
1543
|
+
# so do a light scramble as this appears in the web-ui
|
1544
|
+
zs = ub64enc(hashlib.sha512(s_pw.encode("utf-8")).digest())[4:16]
|
1545
|
+
sun = "s_%s" % (zs.decode("utf-8"),)
|
1521
1546
|
acct[sun] = s_pw
|
1522
1547
|
else:
|
1523
1548
|
sun = "*"
|
@@ -1532,13 +1557,14 @@ class AuthSrv(object):
|
|
1532
1557
|
# don't know the abspath yet + wanna ensure the user
|
1533
1558
|
# still has the privs they granted, so nullmap it
|
1534
1559
|
shv.nodes[s_k] = VFS(
|
1535
|
-
self.log_func, "", "%s/%s" % (shr, s_k), s_axs,
|
1560
|
+
self.log_func, "", "%s/%s" % (shr, s_k), s_axs, shv.flags.copy()
|
1536
1561
|
)
|
1537
1562
|
|
1538
1563
|
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
1539
1564
|
for vol in shv.nodes.values():
|
1540
1565
|
vfs.all_vols[vol.vpath] = vol
|
1541
1566
|
vol.get_dbv = vol._get_share_src
|
1567
|
+
vol.ls = vol._ls_nope
|
1542
1568
|
|
1543
1569
|
zss = set(acct)
|
1544
1570
|
zss.update(self.idp_accs)
|
@@ -2048,6 +2074,9 @@ class AuthSrv(object):
|
|
2048
2074
|
if not self.warn_anonwrite or verbosity < 5:
|
2049
2075
|
break
|
2050
2076
|
|
2077
|
+
if enshare and (zv.vpath == shr or zv.vpath.startswith(shrs)):
|
2078
|
+
continue
|
2079
|
+
|
2051
2080
|
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
2052
2081
|
for txt, attr in [
|
2053
2082
|
[" read", "uread"],
|
@@ -2154,10 +2183,9 @@ class AuthSrv(object):
|
|
2154
2183
|
if x != shr and not x.startswith(shrs)
|
2155
2184
|
}
|
2156
2185
|
|
2157
|
-
assert cur # type: ignore
|
2158
|
-
assert shv # type: ignore
|
2186
|
+
assert db and cur and cur2 and shv # type: ignore
|
2159
2187
|
for row in cur.execute("select * from sh"):
|
2160
|
-
s_k, s_pw, s_vp, s_pr,
|
2188
|
+
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
2161
2189
|
shn = shv.nodes.get(s_k, None)
|
2162
2190
|
if not shn:
|
2163
2191
|
continue
|
@@ -2172,6 +2200,17 @@ class AuthSrv(object):
|
|
2172
2200
|
shv.nodes.pop(s_k)
|
2173
2201
|
continue
|
2174
2202
|
|
2203
|
+
fns = []
|
2204
|
+
if s_nf:
|
2205
|
+
q = "select vp from sf where k = ?"
|
2206
|
+
for (s_fn,) in cur2.execute(q, (s_k,)):
|
2207
|
+
fns.append(s_fn)
|
2208
|
+
|
2209
|
+
shn.shr_files = set(fns)
|
2210
|
+
shn.ls = shn._ls_shr
|
2211
|
+
else:
|
2212
|
+
shn.ls = shn._ls
|
2213
|
+
|
2175
2214
|
shn.shr_src = (s_vfs, s_rem)
|
2176
2215
|
shn.realpath = s_vfs.canonical(s_rem)
|
2177
2216
|
|
@@ -2191,6 +2230,10 @@ class AuthSrv(object):
|
|
2191
2230
|
# hide subvolume
|
2192
2231
|
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
2193
2232
|
|
2233
|
+
cur2.close()
|
2234
|
+
cur.close()
|
2235
|
+
db.close()
|
2236
|
+
|
2194
2237
|
def chpw(self, broker , uname, pw) :
|
2195
2238
|
if not self.args.chpw:
|
2196
2239
|
return False, "feature disabled in server config"
|
copyparty/httpcli.py
CHANGED
@@ -1607,8 +1607,8 @@ class HttpCli(object):
|
|
1607
1607
|
if "delete" in self.uparam:
|
1608
1608
|
return self.handle_rm([])
|
1609
1609
|
|
1610
|
-
if "
|
1611
|
-
return self.
|
1610
|
+
if "eshare" in self.uparam:
|
1611
|
+
return self.handle_eshare()
|
1612
1612
|
|
1613
1613
|
if "application/octet-stream" in ctype:
|
1614
1614
|
return self.handle_post_binary()
|
@@ -3262,7 +3262,8 @@ class HttpCli(object):
|
|
3262
3262
|
raise Exception("not found in registry")
|
3263
3263
|
self.pipes.set(req_path, job)
|
3264
3264
|
except Exception as ex:
|
3265
|
-
|
3265
|
+
if getattr(ex, "errno", 0) != errno.ENOENT:
|
3266
|
+
self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
|
3266
3267
|
ptop = None
|
3267
3268
|
|
3268
3269
|
#
|
@@ -3954,6 +3955,7 @@ class HttpCli(object):
|
|
3954
3955
|
rvol=rvol,
|
3955
3956
|
wvol=wvol,
|
3956
3957
|
avol=avol,
|
3958
|
+
in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
|
3957
3959
|
vstate=vstate,
|
3958
3960
|
scanning=vs["scanning"],
|
3959
3961
|
hashq=vs["hashq"],
|
@@ -4002,10 +4004,10 @@ class HttpCli(object):
|
|
4002
4004
|
def tx_404(self, is_403 = False) :
|
4003
4005
|
rc = 404
|
4004
4006
|
if self.args.vague_403:
|
4005
|
-
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try
|
4006
|
-
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try
|
4007
|
+
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try a password or <a href="{}/?h">go home</a></p>'
|
4008
|
+
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try a password)"
|
4007
4009
|
elif is_403:
|
4008
|
-
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">
|
4010
|
+
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">use a password or <a href="{}/?h">go home</a></p>'
|
4009
4011
|
pt = "403 forbiddena ~┻━┻ (you'll have to log in)"
|
4010
4012
|
rc = 403
|
4011
4013
|
else:
|
@@ -4022,7 +4024,8 @@ class HttpCli(object):
|
|
4022
4024
|
|
4023
4025
|
t = t.format(self.args.SR)
|
4024
4026
|
qv = quotep(self.vpaths) + self.ourlq()
|
4025
|
-
|
4027
|
+
in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:])
|
4028
|
+
html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
|
4026
4029
|
self.reply(html.encode("utf-8"), status=rc)
|
4027
4030
|
return True
|
4028
4031
|
|
@@ -4297,7 +4300,7 @@ class HttpCli(object):
|
|
4297
4300
|
self.reply(html.encode("utf-8"), status=200)
|
4298
4301
|
return True
|
4299
4302
|
|
4300
|
-
def
|
4303
|
+
def handle_eshare(self) :
|
4301
4304
|
idx = self.conn.get_u2idx()
|
4302
4305
|
if not idx or not hasattr(idx, "p_end"):
|
4303
4306
|
if not HAVE_SQLITE3:
|
@@ -4305,7 +4308,7 @@ class HttpCli(object):
|
|
4305
4308
|
raise Pebkac(500, "server busy, cannot create share; please retry in a bit")
|
4306
4309
|
|
4307
4310
|
if self.args.shr_v:
|
4308
|
-
self.log("
|
4311
|
+
self.log("handle_eshare: " + self.req)
|
4309
4312
|
|
4310
4313
|
cur = idx.get_shr()
|
4311
4314
|
if not cur:
|
@@ -4313,18 +4316,36 @@ class HttpCli(object):
|
|
4313
4316
|
|
4314
4317
|
skey = self.vpath.split("/")[-1]
|
4315
4318
|
|
4316
|
-
|
4317
|
-
un =
|
4319
|
+
rows = cur.execute("select un, t1 from sh where k = ?", (skey,)).fetchall()
|
4320
|
+
un = rows[0][0] if rows and rows[0] else ""
|
4318
4321
|
|
4319
4322
|
if not un:
|
4320
4323
|
raise Pebkac(400, "that sharekey didn't match anything")
|
4321
4324
|
|
4325
|
+
expiry = rows[0][1]
|
4326
|
+
|
4322
4327
|
if un != self.uname and self.uname != self.args.shr_adm:
|
4323
4328
|
t = "your username (%r) does not match the sharekey's owner (%r) and you're not admin"
|
4324
4329
|
raise Pebkac(400, t % (self.uname, un))
|
4325
4330
|
|
4326
|
-
|
4331
|
+
reload = False
|
4332
|
+
act = self.uparam["eshare"]
|
4333
|
+
if act == "rm":
|
4334
|
+
cur.execute("delete from sh where k = ?", (skey,))
|
4335
|
+
if skey in self.asrv.vfs.nodes[self.args.shr.strip("/")].nodes:
|
4336
|
+
reload = True
|
4337
|
+
else:
|
4338
|
+
now = time.time()
|
4339
|
+
if expiry < now:
|
4340
|
+
expiry = now
|
4341
|
+
reload = True
|
4342
|
+
expiry += int(act) * 60
|
4343
|
+
cur.execute("update sh set t1 = ? where k = ?", (expiry, skey))
|
4344
|
+
|
4327
4345
|
cur.connection.commit()
|
4346
|
+
if reload:
|
4347
|
+
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
4348
|
+
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
4328
4349
|
|
4329
4350
|
self.redirect(self.args.SRS + "?shares")
|
4330
4351
|
return True
|
@@ -4340,11 +4361,31 @@ class HttpCli(object):
|
|
4340
4361
|
self.log("handle_share: " + json.dumps(req, indent=4))
|
4341
4362
|
|
4342
4363
|
skey = req["k"]
|
4343
|
-
|
4364
|
+
vps = req["vp"]
|
4365
|
+
fns = []
|
4366
|
+
if len(vps) == 1:
|
4367
|
+
vp = vps[0]
|
4368
|
+
if not vp.endswith("/"):
|
4369
|
+
vp, zs = vp.rsplit("/", 1)
|
4370
|
+
fns = [zs]
|
4371
|
+
else:
|
4372
|
+
for zs in vps:
|
4373
|
+
if zs.endswith("/"):
|
4374
|
+
t = "you cannot select more than one folder, or mix flies and folders in one selection"
|
4375
|
+
raise Pebkac(400, t)
|
4376
|
+
vp = vps[0].rsplit("/", 1)[0]
|
4377
|
+
for zs in vps:
|
4378
|
+
vp2, fn = zs.rsplit("/", 1)
|
4379
|
+
fns.append(fn)
|
4380
|
+
if vp != vp2:
|
4381
|
+
t = "mismatching base paths in selection:\n [%s]\n [%s]"
|
4382
|
+
raise Pebkac(400, t % (vp, vp2))
|
4383
|
+
|
4384
|
+
vp = vp.strip("/")
|
4344
4385
|
if self.is_vproxied and (vp == self.args.R or vp.startswith(self.args.RS)):
|
4345
4386
|
vp = vp[len(self.args.RS) :]
|
4346
4387
|
|
4347
|
-
m = re.search(r"([^0-9a-zA-Z_
|
4388
|
+
m = re.search(r"([^0-9a-zA-Z_-])", skey)
|
4348
4389
|
if m:
|
4349
4390
|
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
|
4350
4391
|
|
@@ -4371,29 +4412,41 @@ class HttpCli(object):
|
|
4371
4412
|
except:
|
4372
4413
|
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
4373
4414
|
|
4374
|
-
ap = vfs.
|
4375
|
-
|
4376
|
-
|
4415
|
+
ap, reals, _ = vfs.ls(
|
4416
|
+
rem, self.uname, not self.args.no_scandir, [[s_rd, s_wr, s_mv, s_del]]
|
4417
|
+
)
|
4418
|
+
rfns = set([x[0] for x in reals])
|
4419
|
+
for fn in fns:
|
4420
|
+
if fn not in rfns:
|
4421
|
+
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
4377
4422
|
|
4378
4423
|
pw = req.get("pw") or ""
|
4379
4424
|
now = int(time.time())
|
4380
4425
|
sexp = req["exp"]
|
4381
|
-
exp =
|
4426
|
+
exp = int(sexp) if sexp else 0
|
4427
|
+
exp = now + exp * 60 if exp else 0
|
4382
4428
|
pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
|
4383
4429
|
|
4384
4430
|
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
4385
|
-
cur.execute(q, (skey, pw, vp, pr,
|
4386
|
-
|
4431
|
+
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
4432
|
+
|
4433
|
+
q = "insert into sf values (?,?)"
|
4434
|
+
for fn in fns:
|
4435
|
+
cur.execute(q, (skey, fn))
|
4387
4436
|
|
4437
|
+
cur.connection.commit()
|
4388
4438
|
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
4389
4439
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
4390
4440
|
|
4391
|
-
|
4441
|
+
fn = quotep(fns[0]) if len(fns) == 1 else ""
|
4442
|
+
|
4443
|
+
surl = "created share: %s://%s%s%s%s/%s" % (
|
4392
4444
|
"https" if self.is_https else "http",
|
4393
4445
|
self.host,
|
4394
4446
|
self.args.SR,
|
4395
4447
|
self.args.shr,
|
4396
4448
|
skey,
|
4449
|
+
fn,
|
4397
4450
|
)
|
4398
4451
|
self.loud_reply(surl, status=201)
|
4399
4452
|
return True
|
copyparty/svchub.py
CHANGED
@@ -100,6 +100,7 @@ class SvcHub(object):
|
|
100
100
|
self.no_ansi = args.no_ansi
|
101
101
|
self.logf = None
|
102
102
|
self.logf_base_fn = ""
|
103
|
+
self.is_dut = False # running in unittest; always False
|
103
104
|
self.stop_req = False
|
104
105
|
self.stopping = False
|
105
106
|
self.stopped = False
|
@@ -370,11 +371,18 @@ class SvcHub(object):
|
|
370
371
|
|
371
372
|
import sqlite3
|
372
373
|
|
373
|
-
al.shr =
|
374
|
+
al.shr = al.shr.strip("/")
|
375
|
+
if "/" in al.shr or not al.shr:
|
376
|
+
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
|
377
|
+
self.log("root", t, 1)
|
378
|
+
raise Exception(t)
|
379
|
+
|
380
|
+
al.shr = "/%s/" % (al.shr,)
|
374
381
|
|
375
382
|
create = True
|
383
|
+
modified = False
|
376
384
|
db_path = self.args.shr_db
|
377
|
-
self.log("root", "
|
385
|
+
self.log("root", "opening shares-db %s" % (db_path,))
|
378
386
|
for n in range(2):
|
379
387
|
try:
|
380
388
|
db = sqlite3.connect(db_path)
|
@@ -400,18 +408,43 @@ class SvcHub(object):
|
|
400
408
|
pass
|
401
409
|
os.unlink(db_path)
|
402
410
|
|
411
|
+
sch1 = [
|
412
|
+
r"create table kv (k text, v int)",
|
413
|
+
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
414
|
+
# sharekey, password, src, perms, numFiles, owner, created, expires
|
415
|
+
]
|
416
|
+
sch2 = [
|
417
|
+
r"create table sf (k text, vp text)",
|
418
|
+
r"create index sf_k on sf(k)",
|
419
|
+
r"create index sh_k on sh(k)",
|
420
|
+
r"create index sh_t1 on sh(t1)",
|
421
|
+
]
|
422
|
+
|
403
423
|
assert db # type: ignore
|
404
424
|
assert cur # type: ignore
|
405
425
|
if create:
|
426
|
+
dver = 2
|
427
|
+
modified = True
|
428
|
+
for cmd in sch1 + sch2:
|
429
|
+
cur.execute(cmd)
|
430
|
+
self.log("root", "created new shares-db")
|
431
|
+
else:
|
432
|
+
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
433
|
+
|
434
|
+
if dver == 1:
|
435
|
+
modified = True
|
436
|
+
for cmd in sch2:
|
437
|
+
cur.execute(cmd)
|
438
|
+
cur.execute("update sh set st = 0")
|
439
|
+
self.log("root", "shares-db schema upgrade ok")
|
440
|
+
|
441
|
+
if modified:
|
406
442
|
for cmd in [
|
407
|
-
|
408
|
-
r"
|
409
|
-
r"create table kv (k text, v int)",
|
410
|
-
r"insert into kv values ('sver', {})".format(1),
|
443
|
+
r"delete from kv where k = 'sver'",
|
444
|
+
r"insert into kv values ('sver', %d)" % (2,),
|
411
445
|
]:
|
412
446
|
cur.execute(cmd)
|
413
447
|
db.commit()
|
414
|
-
self.log("root", "created new shares-db")
|
415
448
|
|
416
449
|
cur.close()
|
417
450
|
db.close()
|
copyparty/tftpd.py
CHANGED
@@ -400,7 +400,7 @@ class Tftpd(object):
|
|
400
400
|
bos.stat(ap)
|
401
401
|
return True
|
402
402
|
except:
|
403
|
-
return
|
403
|
+
return vpath == "/"
|
404
404
|
|
405
405
|
def _p_isdir(self, vpath ) :
|
406
406
|
try:
|
@@ -408,7 +408,7 @@ class Tftpd(object):
|
|
408
408
|
ret = stat.S_ISDIR(st.st_mode)
|
409
409
|
return ret
|
410
410
|
except:
|
411
|
-
return
|
411
|
+
return vpath == "/"
|
412
412
|
|
413
413
|
def _hook(self, *a , **ka ) :
|
414
414
|
src = inspect.currentframe().f_back.f_code.co_name
|
copyparty/up2k.py
CHANGED
@@ -233,6 +233,9 @@ class Up2k(object):
|
|
233
233
|
if not self.pp and self.args.exit == "idx":
|
234
234
|
return self.hub.sigterm()
|
235
235
|
|
236
|
+
if self.hub.is_dut:
|
237
|
+
return
|
238
|
+
|
236
239
|
Daemon(self._snapshot, "up2k-snapshot")
|
237
240
|
if have_e2d:
|
238
241
|
Daemon(self._hasher, "up2k-hasher")
|
@@ -429,7 +432,7 @@ class Up2k(object):
|
|
429
432
|
def _sched_rescan(self) :
|
430
433
|
volage = {}
|
431
434
|
cooldown = timeout = time.time() + 3.0
|
432
|
-
while
|
435
|
+
while not self.stop:
|
433
436
|
now = time.time()
|
434
437
|
timeout = max(timeout, cooldown)
|
435
438
|
wait = timeout - time.time()
|
@@ -437,6 +440,9 @@ class Up2k(object):
|
|
437
440
|
with self.rescan_cond:
|
438
441
|
self.rescan_cond.wait(wait)
|
439
442
|
|
443
|
+
if self.stop:
|
444
|
+
return
|
445
|
+
|
440
446
|
now = time.time()
|
441
447
|
if now < cooldown:
|
442
448
|
# self.log("SR: cd - now = {:.2f}".format(cooldown - now), 5)
|
@@ -460,6 +466,7 @@ class Up2k(object):
|
|
460
466
|
if self.args.shr:
|
461
467
|
timeout = min(self._check_shares(), timeout)
|
462
468
|
except Exception as ex:
|
469
|
+
timeout = min(timeout, now + 60)
|
463
470
|
t = "could not check for expiring shares: %r"
|
464
471
|
self.log(t % (ex,), 1)
|
465
472
|
|
@@ -569,27 +576,53 @@ class Up2k(object):
|
|
569
576
|
|
570
577
|
now = time.time()
|
571
578
|
timeout = now + 9001
|
579
|
+
maxage = self.args.shr_rt * 60
|
580
|
+
low = now - maxage
|
581
|
+
|
582
|
+
vn = self.asrv.vfs.nodes.get(self.args.shr.strip("/"))
|
583
|
+
active = vn and vn.nodes
|
572
584
|
|
573
585
|
db = sqlite3.connect(self.args.shr_db, timeout=2)
|
574
586
|
cur = db.cursor()
|
575
587
|
|
576
588
|
q = "select k from sh where t1 and t1 <= ?"
|
577
|
-
rm = [x[0] for x in cur.execute(q, (now,))]
|
589
|
+
rm = [x[0] for x in cur.execute(q, (now,))] if active else []
|
590
|
+
if rm:
|
591
|
+
assert vn and vn.nodes # type: ignore
|
592
|
+
# self.log("chk_shr: %d" % (len(rm),))
|
593
|
+
zss = set(rm)
|
594
|
+
rm = [zs for zs in vn.nodes if zs in zss]
|
595
|
+
reload = bool(rm)
|
596
|
+
if reload:
|
597
|
+
self.log("disabling expired shares %s" % (rm,))
|
598
|
+
|
599
|
+
rm = [x[0] for x in cur.execute(q, (low,))]
|
578
600
|
if rm:
|
579
601
|
self.log("forgetting expired shares %s" % (rm,))
|
580
|
-
|
581
|
-
cur.executemany(
|
602
|
+
cur.executemany("delete from sh where k=?", [(x,) for x in rm])
|
603
|
+
cur.executemany("delete from sf where k=?", [(x,) for x in rm])
|
582
604
|
db.commit()
|
605
|
+
|
606
|
+
if reload:
|
583
607
|
Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
|
584
608
|
|
585
|
-
q = "select min(t1) from sh where t1 >
|
586
|
-
(earliest,) = cur.execute(q).fetchone()
|
609
|
+
q = "select min(t1) from sh where t1 > ?"
|
610
|
+
(earliest,) = cur.execute(q, (1,)).fetchone()
|
587
611
|
if earliest:
|
588
|
-
|
612
|
+
# deadline for revoking regular access
|
613
|
+
timeout = min(timeout, earliest + maxage)
|
614
|
+
|
615
|
+
(earliest,) = cur.execute(q, (now - 2,)).fetchone()
|
616
|
+
if earliest:
|
617
|
+
# deadline for revival; drop entirely
|
618
|
+
timeout = min(timeout, earliest)
|
589
619
|
|
590
620
|
cur.close()
|
591
621
|
db.close()
|
592
622
|
|
623
|
+
if self.args.shr_v:
|
624
|
+
self.log("next shr_chk = %d (%d)" % (timeout, timeout - time.time()))
|
625
|
+
|
593
626
|
return timeout
|
594
627
|
|
595
628
|
def _check_xiu(self) :
|
@@ -1402,7 +1435,7 @@ class Up2k(object):
|
|
1402
1435
|
if dts == lmod and dsz == sz and (nohash or dw[0] != "#" or not sz):
|
1403
1436
|
continue
|
1404
1437
|
|
1405
|
-
t = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
|
1438
|
+
t = "reindex [{}] => [{}] mtime({}/{}) size({}/{})".format(
|
1406
1439
|
top, rp, dts, lmod, dsz, sz
|
1407
1440
|
)
|
1408
1441
|
self.log(t)
|
@@ -2661,11 +2694,19 @@ class Up2k(object):
|
|
2661
2694
|
if stat.S_ISLNK(st.st_mode):
|
2662
2695
|
# broken symlink
|
2663
2696
|
raise Exception()
|
2664
|
-
|
2697
|
+
if st.st_size != dsize:
|
2698
|
+
t = "candidate ignored (db/fs desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
|
2699
|
+
t = t.format(
|
2700
|
+
wark, st.st_size, dsize, st.st_mtime, dtime, dp_abs
|
2701
|
+
)
|
2702
|
+
self.log(t)
|
2703
|
+
raise Exception("desync")
|
2704
|
+
except Exception as ex:
|
2665
2705
|
if n4g:
|
2666
2706
|
st = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
|
2667
2707
|
else:
|
2668
|
-
|
2708
|
+
if str(ex) != "desync":
|
2709
|
+
lost.append((cur, dp_dir, dp_fn))
|
2669
2710
|
continue
|
2670
2711
|
|
2671
2712
|
j = {
|
@@ -2723,13 +2764,16 @@ class Up2k(object):
|
|
2723
2764
|
ptop = None # use cj or job as appropriate
|
2724
2765
|
|
2725
2766
|
if not job and wark in reg:
|
2726
|
-
# ensure the files haven't been deleted
|
2767
|
+
# ensure the files haven't been edited or deleted
|
2768
|
+
path = ""
|
2769
|
+
st = None
|
2727
2770
|
rj = reg[wark]
|
2728
2771
|
names = [rj[x] for x in ["name", "tnam"] if x in rj]
|
2729
2772
|
for fn in names:
|
2730
2773
|
path = djoin(rj["ptop"], rj["prel"], fn)
|
2731
2774
|
try:
|
2732
|
-
|
2775
|
+
st = bos.stat(path)
|
2776
|
+
if st.st_size > 0 or not rj["need"]:
|
2733
2777
|
# upload completed or both present
|
2734
2778
|
break
|
2735
2779
|
except:
|
@@ -2740,6 +2784,14 @@ class Up2k(object):
|
|
2740
2784
|
del reg[wark]
|
2741
2785
|
break
|
2742
2786
|
|
2787
|
+
if st and not self.args.nw and not n4g and st.st_size != rj["size"]:
|
2788
|
+
t = "will not dedup (fs index desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
|
2789
|
+
t = t.format(
|
2790
|
+
wark, st.st_size, rj["size"], st.st_mtime, rj["lmod"], path
|
2791
|
+
)
|
2792
|
+
self.log(t)
|
2793
|
+
del reg[wark]
|
2794
|
+
|
2743
2795
|
if job or wark in reg:
|
2744
2796
|
job = job or reg[wark]
|
2745
2797
|
if (
|
@@ -2847,6 +2899,7 @@ class Up2k(object):
|
|
2847
2899
|
return self._handle_json(job, depth + 1)
|
2848
2900
|
|
2849
2901
|
job["name"] = self._untaken(pdir, job, now)
|
2902
|
+
dst = djoin(job["ptop"], job["prel"], job["name"])
|
2850
2903
|
|
2851
2904
|
if not self.args.nw:
|
2852
2905
|
dvf = vfs.flags
|
copyparty/web/a/u2c.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
S_VERSION = "1.
|
5
|
-
S_BUILD_DT = "2024-08-
|
4
|
+
S_VERSION = "1.23"
|
5
|
+
S_BUILD_DT = "2024-08-22"
|
6
6
|
|
7
7
|
"""
|
8
8
|
u2c.py: upload to copyparty
|
@@ -1236,7 +1236,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|
1236
1236
|
ap.add_argument("-v", action="store_true", help="verbose")
|
1237
1237
|
ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
|
1238
1238
|
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
1239
|
-
ap.add_argument("-x", type=unicode, metavar="REGEX",
|
1239
|
+
ap.add_argument("-x", type=unicode, metavar="REGEX", action="append", help="skip file if filesystem-abspath matches REGEX (option can be repeated), example: '.*/\\.hist/.*'")
|
1240
1240
|
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
1241
1241
|
ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
|
1242
1242
|
ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
|
@@ -1283,6 +1283,8 @@ source file/folder selection uses rsync syntax, meaning that:
|
|
1283
1283
|
if ar.dr:
|
1284
1284
|
ar.ow = True
|
1285
1285
|
|
1286
|
+
ar.x = "|".join(ar.x or [])
|
1287
|
+
|
1286
1288
|
for k in "dl dr drd".split():
|
1287
1289
|
errs = []
|
1288
1290
|
if ar.safe and getattr(ar, k):
|
copyparty/web/browser.css.gz
CHANGED
Binary file
|
copyparty/web/browser.js.gz
CHANGED
Binary file
|
copyparty/web/shares.css.gz
CHANGED
Binary file
|
copyparty/web/shares.html
CHANGED
@@ -15,25 +15,26 @@
|
|
15
15
|
<body>
|
16
16
|
<div id="wrap">
|
17
17
|
<a id="a" href="{{ r }}/?shares" class="af">refresh</a>
|
18
|
-
<a id="a" href="{{ r }}/?h" class="af">
|
18
|
+
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
|
19
19
|
|
20
20
|
<span>axs = perms (read,write,move,delet)</span>
|
21
|
-
<span>
|
21
|
+
<span>nf = numFiles (0=dir)</span>
|
22
22
|
<span>min/hrs = time left</span>
|
23
23
|
|
24
|
-
<table><tr>
|
24
|
+
<table id="tab"><thead><tr>
|
25
25
|
<th>delete</th>
|
26
26
|
<th>sharekey</th>
|
27
27
|
<th>pw</th>
|
28
28
|
<th>source</th>
|
29
29
|
<th>axs</th>
|
30
|
-
<th>
|
30
|
+
<th>nf</th>
|
31
31
|
<th>user</th>
|
32
32
|
<th>created</th>
|
33
33
|
<th>expires</th>
|
34
34
|
<th>min</th>
|
35
35
|
<th>hrs</th>
|
36
|
-
|
36
|
+
<th>add time</th>
|
37
|
+
</tr></thead><tbody>
|
37
38
|
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
|
38
39
|
<tr>
|
39
40
|
<td><a href="#" k="{{ k }}">delete</a></td>
|
@@ -45,11 +46,12 @@
|
|
45
46
|
<td>{{ un|e }}</td>
|
46
47
|
<td>{{ t0 }}</td>
|
47
48
|
<td>{{ t1 }}</td>
|
48
|
-
<td>{{ (t1 - now)
|
49
|
-
<td>{{ (t1 - now)
|
49
|
+
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 60) | round(1) }}</td>
|
50
|
+
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 3600) | round(1) }}</td>
|
51
|
+
<td></td>
|
50
52
|
</tr>
|
51
53
|
{% endfor %}
|
52
|
-
</table>
|
54
|
+
</tbody></table>
|
53
55
|
{% if not rows %}
|
54
56
|
(you don't have any active shares btw)
|
55
57
|
{% endif %}
|
copyparty/web/shares.js.gz
CHANGED
Binary file
|
copyparty/web/splash.css.gz
CHANGED
Binary file
|
copyparty/web/splash.html
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
<body>
|
16
16
|
<div id="wrap">
|
17
|
+
{%- if not in_shr %}
|
17
18
|
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
18
19
|
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
19
20
|
|
@@ -23,6 +24,7 @@
|
|
23
24
|
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
24
25
|
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
|
25
26
|
{%- endif %}
|
27
|
+
{%- endif %}
|
26
28
|
|
27
29
|
{%- if msg %}
|
28
30
|
<div id="msg">
|
@@ -76,6 +78,37 @@
|
|
76
78
|
</ul>
|
77
79
|
{%- endif %}
|
78
80
|
|
81
|
+
{%- if in_shr %}
|
82
|
+
<h1 id="z">unlock this share:</h1>
|
83
|
+
<div>
|
84
|
+
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
85
|
+
<input type="hidden" id="la" name="act" value="login" />
|
86
|
+
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
87
|
+
<input type="hidden" name="uhash" id="uhash" value="x" />
|
88
|
+
<input type="submit" id="ls" value="Unlock" />
|
89
|
+
{% if ahttps %}
|
90
|
+
<a id="w" href="{{ ahttps }}">switch to https</a>
|
91
|
+
{% endif %}
|
92
|
+
</form>
|
93
|
+
</div>
|
94
|
+
{%- else %}
|
95
|
+
<h1 id="l">login for more:</h1>
|
96
|
+
<div>
|
97
|
+
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
98
|
+
<input type="hidden" id="la" name="act" value="login" />
|
99
|
+
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
100
|
+
<input type="hidden" name="uhash" id="uhash" value="x" />
|
101
|
+
<input type="submit" id="ls" value="Login" />
|
102
|
+
{% if chpw %}
|
103
|
+
<a id="x" href="#">change password</a>
|
104
|
+
{% endif %}
|
105
|
+
{% if ahttps %}
|
106
|
+
<a id="w" href="{{ ahttps }}">switch to https</a>
|
107
|
+
{% endif %}
|
108
|
+
</form>
|
109
|
+
</div>
|
110
|
+
{%- endif %}
|
111
|
+
|
79
112
|
<h1 id="cc">other stuff:</h1>
|
80
113
|
<ul>
|
81
114
|
{%- if this.uname != '*' and this.args.shr %}
|
@@ -94,21 +127,6 @@
|
|
94
127
|
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
95
128
|
</ul>
|
96
129
|
|
97
|
-
<h1 id="l">login for more:</h1>
|
98
|
-
<div>
|
99
|
-
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
100
|
-
<input type="hidden" id="la" name="act" value="login" />
|
101
|
-
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
102
|
-
<input type="hidden" name="uhash" id="uhash" value="x" />
|
103
|
-
<input type="submit" id="ls" value="Login" />
|
104
|
-
{% if chpw %}
|
105
|
-
<a id="x" href="#">change password</a>
|
106
|
-
{% endif %}
|
107
|
-
{% if ahttps %}
|
108
|
-
<a id="w" href="{{ ahttps }}">switch to https</a>
|
109
|
-
{% endif %}
|
110
|
-
</form>
|
111
|
-
</div>
|
112
130
|
</div>
|
113
131
|
<a href="#" id="repl">π</a>
|
114
132
|
{%- if not this.args.nb %}
|
copyparty/web/splash.js.gz
CHANGED
Binary file
|
copyparty/web/ui.css.gz
CHANGED
Binary file
|
copyparty/web/util.js.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.14.
|
3
|
+
Version: 1.14.3
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
6
6
|
License: MIT
|
@@ -52,7 +52,9 @@ Requires-Dist: Pillow; extra == "thumbnails"
|
|
52
52
|
Provides-Extra: thumbnails2
|
53
53
|
Requires-Dist: pyvips; extra == "thumbnails2"
|
54
54
|
|
55
|
-
|
55
|
+
<img src="docs/logo.svg" width="250" align="right"/>
|
56
|
+
|
57
|
+
### 💾🎉 copyparty
|
56
58
|
|
57
59
|
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
58
60
|
|
@@ -804,14 +806,16 @@ you can move files across browser tabs (cut in one tab, paste in another)
|
|
804
806
|
|
805
807
|
share a file or folder by creating a temporary link
|
806
808
|
|
807
|
-
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or
|
809
|
+
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or alternatively:
|
810
|
+
* select a folder first to share that folder instead
|
811
|
+
* select one or more files to share only those files
|
808
812
|
|
809
813
|
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
|
810
814
|
|
811
815
|
when creating a share, the creator can choose any of the following options:
|
812
816
|
|
813
817
|
* password-protection
|
814
|
-
* expire after a certain time
|
818
|
+
* expire after a certain time; `0` or blank means infinite
|
815
819
|
* allow visitors to upload (if the user who creates the share has write-access)
|
816
820
|
|
817
821
|
semi-intentional limitations:
|
@@ -822,10 +826,17 @@ semi-intentional limitations:
|
|
822
826
|
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
823
827
|
* browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
|
824
828
|
|
825
|
-
|
829
|
+
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
|
830
|
+
|
831
|
+
* you can name it whatever, `foobar` is just an example
|
832
|
+
* if you're using config files, put `shr: /foobar` inside the `[global]` section instead
|
826
833
|
|
827
834
|
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
|
828
835
|
|
836
|
+
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
|
837
|
+
|
838
|
+
**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.
|
839
|
+
|
829
840
|
|
830
841
|
## batch rename
|
831
842
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
copyparty/__init__.py,sha256=fUINM1abqDGzCCH_JcXdOnLdKOV-SrTI2Xo2QgQW2P4,1703
|
2
|
-
copyparty/__main__.py,sha256=
|
3
|
-
copyparty/__version__.py,sha256=
|
4
|
-
copyparty/authsrv.py,sha256=
|
2
|
+
copyparty/__main__.py,sha256=0fQcGqHkcYXRqtkh9as-xyBfV_5R6z2fQf41WFtjRe8,108093
|
3
|
+
copyparty/__version__.py,sha256=4Eh8URLJCrO8OCohdoeL5DfefMT0SlqqmOBPsnjPgJE,258
|
4
|
+
copyparty/authsrv.py,sha256=jWXTjZLT8cGymfa9wBwGJqyBIi80aXcvzAMgI74G8iA,95750
|
5
5
|
copyparty/broker_mp.py,sha256=YFe1S6Zziht8Qc__dCLj_ff8z0DDny9lqk_Mi5ajsJk,3868
|
6
6
|
copyparty/broker_mpw.py,sha256=4ZI7bJYOwUibeAJVv9_FPGNmHrr9eOtkj_Kz0JEppTU,3197
|
7
7
|
copyparty/broker_thr.py,sha256=eKr--HJGig5zqvNGwH9UoBG9Nvi9mT2axrRmJwknd0s,1759
|
@@ -11,7 +11,7 @@ copyparty/cfg.py,sha256=i8-bjWgbguQooxiA172RcptqR_SEOwDHJ4cqldrZ8oQ,9792
|
|
11
11
|
copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
|
12
12
|
copyparty/fsutil.py,sha256=hnEHgySI43-XJJKbI8n6t1A6oVHzR_nYdsBcAwtreBk,4610
|
13
13
|
copyparty/ftpd.py,sha256=1vD-KTy07xfEEEk1dx37pUYModpNO2gIhVXvFUr205M,17497
|
14
|
-
copyparty/httpcli.py,sha256=
|
14
|
+
copyparty/httpcli.py,sha256=QTOrZOuVh__C_r5REpTiUeGEhrRAke2ejEOOi4zyfbo,182619
|
15
15
|
copyparty/httpconn.py,sha256=mwIDup85cBowIfJOse8rla5bqTz7nf-ChgfR-5-V0JM,6938
|
16
16
|
copyparty/httpsrv.py,sha256=8_1Ivg3eco7HJDjqL_rUB58IOUaUnoXGhO62bOMXLBk,17242
|
17
17
|
copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
|
@@ -24,14 +24,14 @@ copyparty/smbd.py,sha256=8zkC9BjVtGiKXMLajbdakxoKeFzACdM75SW0_SvqXJA,14490
|
|
24
24
|
copyparty/ssdp.py,sha256=8iyF5sqIjATJLWcAtnJa8eadHosOn0CP4ywltzJ7bVY,7023
|
25
25
|
copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
|
26
26
|
copyparty/sutil.py,sha256=JTMrQwcWH85hXB_cKG206eDZ967WZDGaP00AWvl_gB0,3214
|
27
|
-
copyparty/svchub.py,sha256=
|
27
|
+
copyparty/svchub.py,sha256=v0f8KU65dj2MXOjrd-7kckgiFZSu8kpnTUMyLfn9NIM,38429
|
28
28
|
copyparty/szip.py,sha256=tor4yjdHhEL4Ox-Xg7-cuUFrMO0IwQD29aRX5Cp8MYs,8605
|
29
29
|
copyparty/tcpsrv.py,sha256=jM_Za64O8LEMfMrU4irJluIJZrU494e2b759r_KhaUQ,19881
|
30
|
-
copyparty/tftpd.py,sha256=
|
30
|
+
copyparty/tftpd.py,sha256=jZbf2JpeJmkuQWJErmAPG-dKhtYNvIUHbkAgodSXw9Y,13582
|
31
31
|
copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
|
32
32
|
copyparty/th_srv.py,sha256=27IftjIXUQzRRiUytt-CgXkybEoP3HHHoXaDAvxEmLo,29217
|
33
33
|
copyparty/u2idx.py,sha256=t4mzjj2GDrkjIHt0RM68y1EgT5qOBoz6mkYgjMbqA38,13526
|
34
|
-
copyparty/up2k.py,sha256=
|
34
|
+
copyparty/up2k.py,sha256=kfzsgd-dHjoWUETP79rq46n8oZDU0uDlDlIL3tHbUis,155260
|
35
35
|
copyparty/util.py,sha256=S7FuQBPbl2FX7ULIH9VNgiB7Z_rceqTJssXz4SGErwA,88625
|
36
36
|
copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
37
|
copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
|
@@ -55,9 +55,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
|
|
55
55
|
copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
|
56
56
|
copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
|
57
57
|
copyparty/web/baguettebox.js.gz,sha256=4dS8-r4si84ca71l98672ahnRI86Aq95MU-bc5knykk,7962
|
58
|
-
copyparty/web/browser.css.gz,sha256=
|
58
|
+
copyparty/web/browser.css.gz,sha256=PoW_IIwFigZaMo3atpPU0o05Jj5Flbsm1bhW_KfcX-U,11491
|
59
59
|
copyparty/web/browser.html,sha256=vvfWiu_aOFRar8u5lridMRKQSPF4R0YkA41zrsh82Qs,4878
|
60
|
-
copyparty/web/browser.js.gz,sha256=
|
60
|
+
copyparty/web/browser.js.gz,sha256=vbgNK0skbOyQHqUeYbLxi0qkdAXzWBTGxhgFaKBhxx8,80863
|
61
61
|
copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
|
62
62
|
copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
|
63
63
|
copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
|
@@ -71,21 +71,21 @@ copyparty/web/mde.html,sha256=ImBhQAaEUCke2M85QU_fl4X2XQExRLcEzgCEN8RNe9o,1759
|
|
71
71
|
copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
|
72
72
|
copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
|
73
73
|
copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
|
74
|
-
copyparty/web/shares.css.gz,sha256=
|
75
|
-
copyparty/web/shares.html,sha256=
|
76
|
-
copyparty/web/shares.js.gz,sha256=
|
77
|
-
copyparty/web/splash.css.gz,sha256=
|
78
|
-
copyparty/web/splash.html,sha256=
|
79
|
-
copyparty/web/splash.js.gz,sha256=
|
74
|
+
copyparty/web/shares.css.gz,sha256=m-nRqTGPiU3ohZxvGaROzFr98F_jmohQnjieqEAyjBo,496
|
75
|
+
copyparty/web/shares.html,sha256=d--9tyg6u3JzszpEtMmU5S4XdUF_mfUAhzCvwl-XAXw,2384
|
76
|
+
copyparty/web/shares.js.gz,sha256=296fTZV4sW7CxT-YNnDufUZL-aIy4E4r8q-XtSy6bHs,652
|
77
|
+
copyparty/web/splash.css.gz,sha256=4DOtEKBWyaDKel7fdnwvnc9FrKlkht-ec7R2nRlruPU,1023
|
78
|
+
copyparty/web/splash.html,sha256=dAo4KXKmXUMGcIwetZkFtVxk-mCMNkscD36BxLwRdow,4804
|
79
|
+
copyparty/web/splash.js.gz,sha256=pxEHaRDpxTnW6WdRWpKlRux8jtI7B5RImRjUVs9gdQQ,2582
|
80
80
|
copyparty/web/svcs.html,sha256=v0C3cOFWXYlvp3GEifz1Qj0W3MD8JANT3WTON05GZ9o,11797
|
81
81
|
copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
|
82
|
-
copyparty/web/ui.css.gz,sha256=
|
82
|
+
copyparty/web/ui.css.gz,sha256=ae1JosPYS8d2F9e_b95bTwa7qYwk8Ur_UhoVpRYEp0Y,2658
|
83
83
|
copyparty/web/up2k.js.gz,sha256=KufMtRViAZQo2rVj67iEWbdPxlVeXW85emRYVJoY3aA,22946
|
84
|
-
copyparty/web/util.js.gz,sha256=
|
84
|
+
copyparty/web/util.js.gz,sha256=dPuhXEBJ_T-d2tYUUufGTUul4FYIbuh6GQmtK7iBkEo,14682
|
85
85
|
copyparty/web/w.hash.js.gz,sha256=7wP9EZQNXQxwZnCCFUVsi_-6TM9PLZJeZ9krutXRRj8,1060
|
86
86
|
copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
87
87
|
copyparty/web/a/partyfuse.py,sha256=MuRkaSuYsdfWfBFMOkbPwDXqSvNTw3sd7QhhlKCDZ8I,32311
|
88
|
-
copyparty/web/a/u2c.py,sha256=
|
88
|
+
copyparty/web/a/u2c.py,sha256=WG9njRxY9g9xjO93-fas9Wo-AM8vtrWrUTwpJ5Afmvk,42482
|
89
89
|
copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
|
90
90
|
copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
|
91
91
|
copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
|
@@ -105,9 +105,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
|
|
105
105
|
copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
|
106
106
|
copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
|
107
107
|
copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
|
108
|
-
copyparty-1.14.
|
109
|
-
copyparty-1.14.
|
110
|
-
copyparty-1.14.
|
111
|
-
copyparty-1.14.
|
112
|
-
copyparty-1.14.
|
113
|
-
copyparty-1.14.
|
108
|
+
copyparty-1.14.3.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
109
|
+
copyparty-1.14.3.dist-info/METADATA,sha256=qu9Hp-ogYR98wuQgVMJNJOhOzNAIRKgGO7erkv72ty0,131776
|
110
|
+
copyparty-1.14.3.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
111
|
+
copyparty-1.14.3.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
112
|
+
copyparty-1.14.3.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
113
|
+
copyparty-1.14.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|