copyparty 1.15.2__py3-none-any.whl → 1.15.4__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/__init__.py +55 -0
- copyparty/__main__.py +15 -12
- copyparty/__version__.py +2 -2
- copyparty/cert.py +20 -8
- copyparty/cfg.py +1 -1
- copyparty/ftpd.py +1 -1
- copyparty/httpcli.py +227 -68
- copyparty/httpconn.py +0 -3
- copyparty/httpsrv.py +10 -15
- copyparty/metrics.py +1 -1
- copyparty/smbd.py +6 -3
- copyparty/stolen/qrcodegen.py +17 -0
- copyparty/szip.py +1 -1
- copyparty/u2idx.py +1 -0
- copyparty/up2k.py +31 -13
- copyparty/util.py +145 -64
- copyparty/web/a/partyfuse.py +233 -357
- copyparty/web/a/u2c.py +249 -153
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +3 -6
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/fuse.py +1064 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/shares.css.gz +0 -0
- copyparty/web/shares.html +7 -4
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.html +12 -12
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -1
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/METADATA +9 -8
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/RECORD +37 -36
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/WHEEL +1 -1
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/LICENSE +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/entry_points.txt +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/top_level.txt +0 -0
copyparty/szip.py
CHANGED
@@ -99,7 +99,7 @@ def gen_hdr(
|
|
99
99
|
ret += spack(b"<LL", vsz, vsz)
|
100
100
|
|
101
101
|
# windows support (the "?" replace below too)
|
102
|
-
fn = sanitize_fn(fn, "/"
|
102
|
+
fn = sanitize_fn(fn, "/")
|
103
103
|
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
104
104
|
|
105
105
|
# add ntfs (0x24) and/or unix (0x10) extrafields for utc, add z64 if requested
|
copyparty/u2idx.py
CHANGED
copyparty/up2k.py
CHANGED
@@ -285,7 +285,9 @@ class Up2k(object):
|
|
285
285
|
else:
|
286
286
|
mtpq = "(?)"
|
287
287
|
if up_en:
|
288
|
-
ups = [(0, 0, time.time(), "cannot show list (server too busy)")]
|
288
|
+
ups = [(1, 0, 0, time.time(), "cannot show list (server too busy)")]
|
289
|
+
if PY2:
|
290
|
+
ups = []
|
289
291
|
|
290
292
|
ups.sort(reverse=True)
|
291
293
|
|
@@ -322,7 +324,7 @@ class Up2k(object):
|
|
322
324
|
zt = (
|
323
325
|
ineed / ihash,
|
324
326
|
job["size"],
|
325
|
-
int(job["
|
327
|
+
int(job["t0c"]),
|
326
328
|
int(job["poke"]),
|
327
329
|
djoin(vtop, job["prel"], job["name"]),
|
328
330
|
)
|
@@ -358,10 +360,9 @@ class Up2k(object):
|
|
358
360
|
continue
|
359
361
|
addr = (ip or "\n") if cfg in (1, 2) else ""
|
360
362
|
user = (uname or "\n") if cfg in (1, 3) else ""
|
361
|
-
|
362
|
-
for wark, job in tab2.items():
|
363
|
+
for job in tab2.values():
|
363
364
|
if (
|
364
|
-
|
365
|
+
"done" in job
|
365
366
|
or (user and user != job["user"])
|
366
367
|
or (addr and addr != job["addr"])
|
367
368
|
):
|
@@ -402,9 +403,8 @@ class Up2k(object):
|
|
402
403
|
for ptop, tab2 in self.registry.items():
|
403
404
|
nbytes = 0
|
404
405
|
nfiles = 0
|
405
|
-
|
406
|
-
|
407
|
-
if wark in drp:
|
406
|
+
for job in tab2.values():
|
407
|
+
if "done" in job:
|
408
408
|
continue
|
409
409
|
|
410
410
|
nfiles += 1
|
@@ -1048,6 +1048,7 @@ class Up2k(object):
|
|
1048
1048
|
|
1049
1049
|
reg = {}
|
1050
1050
|
drp = None
|
1051
|
+
emptylist = []
|
1051
1052
|
snap = os.path.join(histpath, "up2k.snap")
|
1052
1053
|
if bos.path.exists(snap):
|
1053
1054
|
with gzip.GzipFile(snap, "rb") as f:
|
@@ -1064,6 +1065,9 @@ class Up2k(object):
|
|
1064
1065
|
fp = djoin(job["ptop"], job["prel"], job["name"])
|
1065
1066
|
if bos.path.exists(fp):
|
1066
1067
|
reg[k] = job
|
1068
|
+
if "done" in job:
|
1069
|
+
job["need"] = job["hash"] = emptylist
|
1070
|
+
continue
|
1067
1071
|
job["poke"] = time.time()
|
1068
1072
|
job["busy"] = {}
|
1069
1073
|
else:
|
@@ -2758,7 +2762,7 @@ class Up2k(object):
|
|
2758
2762
|
if ptop not in self.registry:
|
2759
2763
|
raise Pebkac(410, "location unavailable")
|
2760
2764
|
|
2761
|
-
cj["name"] = sanitize_fn(cj["name"], ""
|
2765
|
+
cj["name"] = sanitize_fn(cj["name"], "")
|
2762
2766
|
cj["poke"] = now = self.db_act = self.vol_act[ptop] = time.time()
|
2763
2767
|
wark = self._get_wark(cj)
|
2764
2768
|
job = None
|
@@ -3003,7 +3007,7 @@ class Up2k(object):
|
|
3003
3007
|
|
3004
3008
|
job = deepcopy(job)
|
3005
3009
|
job["wark"] = wark
|
3006
|
-
job["at"] = cj.get("at") or
|
3010
|
+
job["at"] = cj.get("at") or now
|
3007
3011
|
zs = "vtop ptop prel name lmod host user addr poke"
|
3008
3012
|
for k in zs.split():
|
3009
3013
|
job[k] = cj.get(k) or ""
|
@@ -3328,6 +3332,9 @@ class Up2k(object):
|
|
3328
3332
|
self.log("unknown wark [{}], known: {}".format(wark, known))
|
3329
3333
|
raise Pebkac(400, "unknown wark" + SSEELOG)
|
3330
3334
|
|
3335
|
+
if "t0c" not in job:
|
3336
|
+
job["t0c"] = time.time()
|
3337
|
+
|
3331
3338
|
if len(chashes) > 1 and len(chashes[1]) < 44:
|
3332
3339
|
# first hash is full-length; expand remaining ones
|
3333
3340
|
uniq = []
|
@@ -3508,7 +3515,11 @@ class Up2k(object):
|
|
3508
3515
|
if self.idx_wark(vflags, *z2):
|
3509
3516
|
del self.registry[ptop][wark]
|
3510
3517
|
else:
|
3511
|
-
|
3518
|
+
for k in "host tnam busy sprs poke t0c".split():
|
3519
|
+
del job[k]
|
3520
|
+
job["t0"] = int(job["t0"])
|
3521
|
+
job["hash"] = []
|
3522
|
+
job["done"] = 1
|
3512
3523
|
self.regdrop(ptop, wark)
|
3513
3524
|
|
3514
3525
|
if wake_sr:
|
@@ -4695,7 +4706,11 @@ class Up2k(object):
|
|
4695
4706
|
bos.unlink(path)
|
4696
4707
|
return
|
4697
4708
|
|
4698
|
-
newest = float(
|
4709
|
+
newest = float(
|
4710
|
+
max(x["t0"] if "done" in x else x["poke"] for _, x in reg.items())
|
4711
|
+
if reg
|
4712
|
+
else 0
|
4713
|
+
)
|
4699
4714
|
etag = (len(reg), newest)
|
4700
4715
|
if etag == self.snap_prev.get(ptop):
|
4701
4716
|
return
|
@@ -4706,12 +4721,15 @@ class Up2k(object):
|
|
4706
4721
|
path2 = "{}.{}".format(path, os.getpid())
|
4707
4722
|
body = {"droppable": self.droppable[ptop], "registry": reg}
|
4708
4723
|
j = json.dumps(body, sort_keys=True, separators=(",\n", ": ")).encode("utf-8")
|
4724
|
+
# j = re.sub(r'"(need|hash)": \[\],\n', "", j) # bytes=slow, utf8=hungry
|
4725
|
+
j = j.replace(b'"need": [],\n', b"") # surprisingly optimal
|
4726
|
+
j = j.replace(b'"hash": [],\n', b"")
|
4709
4727
|
with gzip.GzipFile(path2, "wb") as f:
|
4710
4728
|
f.write(j)
|
4711
4729
|
|
4712
4730
|
atomic_move(self.log, path2, path, VF_CAREFUL)
|
4713
4731
|
|
4714
|
-
self.log("snap:
|
4732
|
+
self.log("snap: %s |%d| %.2fs" % (path, len(reg), time.time() - now))
|
4715
4733
|
self.snap_prev[ptop] = etag
|
4716
4734
|
|
4717
4735
|
def _tagger(self) :
|
copyparty/util.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|
4
4
|
import argparse
|
5
5
|
import base64
|
6
6
|
import binascii
|
7
|
+
import codecs
|
7
8
|
import errno
|
8
9
|
import hashlib
|
9
10
|
import hmac
|
@@ -30,7 +31,17 @@ from collections import Counter
|
|
30
31
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
31
32
|
from queue import Queue
|
32
33
|
|
33
|
-
from .__init__ import
|
34
|
+
from .__init__ import (
|
35
|
+
ANYWIN,
|
36
|
+
EXE,
|
37
|
+
MACOS,
|
38
|
+
PY2,
|
39
|
+
PY36,
|
40
|
+
TYPE_CHECKING,
|
41
|
+
VT100,
|
42
|
+
WINDOWS,
|
43
|
+
EnvParams,
|
44
|
+
)
|
34
45
|
from .__version__ import S_BUILD_DT, S_VERSION
|
35
46
|
from .stolen import surrogateescape
|
36
47
|
|
@@ -258,6 +269,30 @@ if ANYWIN:
|
|
258
269
|
UNPLICATIONS = [["no_dav", "daw"]]
|
259
270
|
|
260
271
|
|
272
|
+
DAV_ALLPROP_L = [
|
273
|
+
"contentclass",
|
274
|
+
"creationdate",
|
275
|
+
"defaultdocument",
|
276
|
+
"displayname",
|
277
|
+
"getcontentlanguage",
|
278
|
+
"getcontentlength",
|
279
|
+
"getcontenttype",
|
280
|
+
"getlastmodified",
|
281
|
+
"href",
|
282
|
+
"iscollection",
|
283
|
+
"ishidden",
|
284
|
+
"isreadonly",
|
285
|
+
"isroot",
|
286
|
+
"isstructureddocument",
|
287
|
+
"lastaccessed",
|
288
|
+
"name",
|
289
|
+
"parentname",
|
290
|
+
"resourcetype",
|
291
|
+
"supportedlock",
|
292
|
+
]
|
293
|
+
DAV_ALLPROPS = set(DAV_ALLPROP_L)
|
294
|
+
|
295
|
+
|
261
296
|
MIMES = {
|
262
297
|
"opus": "audio/ogg; codecs=opus",
|
263
298
|
}
|
@@ -728,60 +763,6 @@ class _Unrecv(object):
|
|
728
763
|
self.buf = buf + self.buf
|
729
764
|
|
730
765
|
|
731
|
-
class _LUnrecv(object):
|
732
|
-
"""
|
733
|
-
with expensive debug logging
|
734
|
-
"""
|
735
|
-
|
736
|
-
def __init__(self, s , log ) :
|
737
|
-
self.s = s
|
738
|
-
self.log = log
|
739
|
-
self.buf = b""
|
740
|
-
|
741
|
-
def recv(self, nbytes , spins ) :
|
742
|
-
if self.buf:
|
743
|
-
ret = self.buf[:nbytes]
|
744
|
-
self.buf = self.buf[nbytes:]
|
745
|
-
t = "\033[0;7mur:pop:\033[0;1;32m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
|
746
|
-
print(t.format(ret, self.buf))
|
747
|
-
return ret
|
748
|
-
|
749
|
-
ret = self.s.recv(nbytes)
|
750
|
-
t = "\033[0;7mur:recv\033[0;1;33m {}\033[0m"
|
751
|
-
print(t.format(ret))
|
752
|
-
if not ret:
|
753
|
-
raise UnrecvEOF("client stopped sending data")
|
754
|
-
|
755
|
-
return ret
|
756
|
-
|
757
|
-
def recv_ex(self, nbytes , raise_on_trunc = True) :
|
758
|
-
"""read an exact number of bytes"""
|
759
|
-
try:
|
760
|
-
ret = self.recv(nbytes, 1)
|
761
|
-
err = False
|
762
|
-
except:
|
763
|
-
ret = b""
|
764
|
-
err = True
|
765
|
-
|
766
|
-
while not err and len(ret) < nbytes:
|
767
|
-
try:
|
768
|
-
ret += self.recv(nbytes - len(ret), 1)
|
769
|
-
except OSError:
|
770
|
-
err = True
|
771
|
-
|
772
|
-
if err:
|
773
|
-
t = "client only sent {} of {} expected bytes".format(len(ret), nbytes)
|
774
|
-
if raise_on_trunc:
|
775
|
-
raise UnrecvEOF(t)
|
776
|
-
elif self.log:
|
777
|
-
self.log(t, 3)
|
778
|
-
|
779
|
-
return ret
|
780
|
-
|
781
|
-
def unrecv(self, buf ) :
|
782
|
-
self.buf = buf + self.buf
|
783
|
-
t = "\033[0;7mur:push\033[0;1;31m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
|
784
|
-
print(t.format(buf, self.buf))
|
785
766
|
|
786
767
|
|
787
768
|
Unrecv = _Unrecv
|
@@ -1916,13 +1897,10 @@ def undot(path ) :
|
|
1916
1897
|
return "/".join(ret)
|
1917
1898
|
|
1918
1899
|
|
1919
|
-
def sanitize_fn(fn , ok
|
1900
|
+
def sanitize_fn(fn , ok ) :
|
1920
1901
|
if "/" not in ok:
|
1921
1902
|
fn = fn.replace("\\", "/").split("/")[-1]
|
1922
1903
|
|
1923
|
-
if fn.lower() in bad:
|
1924
|
-
fn = "_" + fn
|
1925
|
-
|
1926
1904
|
if ANYWIN:
|
1927
1905
|
remap = [
|
1928
1906
|
["<", "<"],
|
@@ -1948,9 +1926,9 @@ def sanitize_fn(fn , ok , bad ) :
|
|
1948
1926
|
return fn.strip()
|
1949
1927
|
|
1950
1928
|
|
1951
|
-
def sanitize_vpath(vp , ok
|
1929
|
+
def sanitize_vpath(vp , ok ) :
|
1952
1930
|
parts = vp.replace(os.sep, "/").split("/")
|
1953
|
-
ret = [sanitize_fn(x, ok
|
1931
|
+
ret = [sanitize_fn(x, ok) for x in parts]
|
1954
1932
|
return "/".join(ret)
|
1955
1933
|
|
1956
1934
|
|
@@ -3376,9 +3354,15 @@ def loadpy(ap , hot ) :
|
|
3376
3354
|
|
3377
3355
|
def gzip_orig_sz(fn ) :
|
3378
3356
|
with open(fsenc(fn), "rb") as f:
|
3379
|
-
f
|
3380
|
-
|
3381
|
-
|
3357
|
+
return gzip_file_orig_sz(f)
|
3358
|
+
|
3359
|
+
|
3360
|
+
def gzip_file_orig_sz(f) :
|
3361
|
+
start = f.tell()
|
3362
|
+
f.seek(-4, 2)
|
3363
|
+
rv = f.read(4)
|
3364
|
+
f.seek(start, 0)
|
3365
|
+
return sunpack(b"I", rv)[0] # type: ignore
|
3382
3366
|
|
3383
3367
|
|
3384
3368
|
def align_tab(lines ) :
|
@@ -3513,6 +3497,103 @@ def hidedir(dp) :
|
|
3513
3497
|
pass
|
3514
3498
|
|
3515
3499
|
|
3500
|
+
try:
|
3501
|
+
if sys.version_info < (3, 10):
|
3502
|
+
# py3.8 doesn't have .files
|
3503
|
+
# py3.9 has broken .is_file
|
3504
|
+
raise ImportError()
|
3505
|
+
import importlib.resources as impresources
|
3506
|
+
except ImportError:
|
3507
|
+
try:
|
3508
|
+
import importlib_resources as impresources
|
3509
|
+
except ImportError:
|
3510
|
+
impresources = None
|
3511
|
+
try:
|
3512
|
+
if sys.version_info > (3, 10):
|
3513
|
+
raise ImportError()
|
3514
|
+
import pkg_resources
|
3515
|
+
except ImportError:
|
3516
|
+
pkg_resources = None
|
3517
|
+
|
3518
|
+
|
3519
|
+
def _pkg_resource_exists(pkg , name ) :
|
3520
|
+
if not pkg_resources:
|
3521
|
+
return False
|
3522
|
+
try:
|
3523
|
+
return pkg_resources.resource_exists(pkg, name)
|
3524
|
+
except NotImplementedError:
|
3525
|
+
return False
|
3526
|
+
|
3527
|
+
|
3528
|
+
def stat_resource(E , name ):
|
3529
|
+
path = os.path.join(E.mod, name)
|
3530
|
+
if os.path.exists(path):
|
3531
|
+
return os.stat(fsenc(path))
|
3532
|
+
return None
|
3533
|
+
|
3534
|
+
|
3535
|
+
def _find_impresource(E , name ):
|
3536
|
+
try:
|
3537
|
+
files = impresources.files(E.pkg)
|
3538
|
+
except ImportError:
|
3539
|
+
return None
|
3540
|
+
|
3541
|
+
return files.joinpath(name)
|
3542
|
+
|
3543
|
+
|
3544
|
+
_rescache_has = {}
|
3545
|
+
|
3546
|
+
|
3547
|
+
def _has_resource(E , name ):
|
3548
|
+
try:
|
3549
|
+
return _rescache_has[name]
|
3550
|
+
except:
|
3551
|
+
pass
|
3552
|
+
|
3553
|
+
if len(_rescache_has) > 999:
|
3554
|
+
_rescache_has.clear()
|
3555
|
+
|
3556
|
+
if impresources:
|
3557
|
+
res = _find_impresource(E, name)
|
3558
|
+
if res and res.is_file():
|
3559
|
+
_rescache_has[name] = True
|
3560
|
+
return True
|
3561
|
+
|
3562
|
+
if pkg_resources:
|
3563
|
+
if _pkg_resource_exists(E.pkg.__name__, name):
|
3564
|
+
_rescache_has[name] = True
|
3565
|
+
return True
|
3566
|
+
|
3567
|
+
_rescache_has[name] = False
|
3568
|
+
return False
|
3569
|
+
|
3570
|
+
|
3571
|
+
def has_resource(E , name ):
|
3572
|
+
return _has_resource(E, name) or os.path.exists(os.path.join(E.mod, name))
|
3573
|
+
|
3574
|
+
|
3575
|
+
def load_resource(E , name , mode="rb") :
|
3576
|
+
enc = None if "b" in mode else "utf-8"
|
3577
|
+
|
3578
|
+
if impresources:
|
3579
|
+
res = _find_impresource(E, name)
|
3580
|
+
if res and res.is_file():
|
3581
|
+
if enc:
|
3582
|
+
return res.open(mode, encoding=enc)
|
3583
|
+
else:
|
3584
|
+
# throws if encoding= is mentioned at all
|
3585
|
+
return res.open(mode)
|
3586
|
+
|
3587
|
+
if pkg_resources:
|
3588
|
+
if _pkg_resource_exists(E.pkg.__name__, name):
|
3589
|
+
stream = pkg_resources.resource_stream(E.pkg.__name__, name)
|
3590
|
+
if enc:
|
3591
|
+
stream = codecs.getreader(enc)(stream)
|
3592
|
+
return stream
|
3593
|
+
|
3594
|
+
return open(os.path.join(E.mod, name), mode, encoding=enc)
|
3595
|
+
|
3596
|
+
|
3516
3597
|
class Pebkac(Exception):
|
3517
3598
|
def __init__(
|
3518
3599
|
self, code , msg = None, log = None
|