copyparty 1.16.17__py3-none-any.whl → 1.16.19__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 +1 -1
- copyparty/__main__.py +26 -2
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +62 -9
- copyparty/cfg.py +5 -0
- copyparty/ftpd.py +11 -0
- copyparty/httpcli.py +57 -19
- copyparty/ico.py +13 -2
- copyparty/mtag.py +1 -2
- copyparty/pwhash.py +1 -1
- copyparty/svchub.py +151 -63
- copyparty/szip.py +1 -2
- copyparty/tcpsrv.py +18 -0
- copyparty/tftpd.py +29 -8
- copyparty/th_cli.py +23 -4
- copyparty/th_srv.py +16 -3
- copyparty/u2idx.py +2 -2
- copyparty/up2k.py +12 -7
- copyparty/util.py +110 -5
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info}/METADATA +18 -5
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info}/RECORD +29 -29
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info}/WHEEL +1 -1
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info}/entry_points.txt +0 -0
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info/licenses}/LICENSE +0 -0
- {copyparty-1.16.17.dist-info → copyparty-1.16.19.dist-info}/top_level.txt +0 -0
copyparty/svchub.py
CHANGED
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
|
|
3
3
|
|
4
4
|
import argparse
|
5
5
|
import errno
|
6
|
-
import gzip
|
7
6
|
import logging
|
8
7
|
import os
|
9
8
|
import re
|
@@ -57,7 +56,9 @@ from .util import (
|
|
57
56
|
ansi_re,
|
58
57
|
build_netmap,
|
59
58
|
expat_ver,
|
59
|
+
gzip,
|
60
60
|
load_ipu,
|
61
|
+
lock_file,
|
61
62
|
min_ex,
|
62
63
|
mp,
|
63
64
|
odfusion,
|
@@ -67,6 +68,9 @@ from .util import (
|
|
67
68
|
ub64enc,
|
68
69
|
)
|
69
70
|
|
71
|
+
if HAVE_SQLITE3:
|
72
|
+
import sqlite3
|
73
|
+
|
70
74
|
if TYPE_CHECKING:
|
71
75
|
try:
|
72
76
|
from .mdns import MDNS
|
@@ -78,6 +82,10 @@ if PY2:
|
|
78
82
|
range = xrange # type: ignore
|
79
83
|
|
80
84
|
|
85
|
+
VER_SESSION_DB = 1
|
86
|
+
VER_SHARES_DB = 2
|
87
|
+
|
88
|
+
|
81
89
|
class SvcHub(object):
|
82
90
|
"""
|
83
91
|
Hosts all services which cannot be parallelized due to reliance on monolithic resources.
|
@@ -180,8 +188,14 @@ class SvcHub(object):
|
|
180
188
|
|
181
189
|
if not args.use_fpool and args.j != 1:
|
182
190
|
args.no_fpool = True
|
183
|
-
t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems"
|
184
|
-
|
191
|
+
t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems, and make some antivirus-softwares "
|
192
|
+
c = 0
|
193
|
+
if ANYWIN:
|
194
|
+
t += "(especially Microsoft Defender) stress your CPU and HDD severely during big uploads"
|
195
|
+
c = 3
|
196
|
+
else:
|
197
|
+
t += "consume more resources (CPU/HDD) than normal"
|
198
|
+
self.log("root", t.format(args.j), c)
|
185
199
|
|
186
200
|
if not args.no_fpool and args.j != 1:
|
187
201
|
t = "WARNING: ignoring --use-fpool because multithreading (-j{}) is enabled"
|
@@ -400,25 +414,48 @@ class SvcHub(object):
|
|
400
414
|
self.log("root", t, 3)
|
401
415
|
return
|
402
416
|
|
403
|
-
import sqlite3
|
404
417
|
|
405
|
-
|
418
|
+
# policy:
|
419
|
+
# the sessions-db is whatever, if something looks broken then just nuke it
|
420
|
+
|
406
421
|
db_path = self.args.ses_db
|
407
|
-
|
408
|
-
|
422
|
+
db_lock = db_path + ".lock"
|
423
|
+
try:
|
424
|
+
create = not os.path.getsize(db_path)
|
425
|
+
except:
|
426
|
+
create = True
|
427
|
+
zs = "creating new" if create else "opening"
|
428
|
+
self.log("root", "%s sessions-db %s" % (zs, db_path))
|
429
|
+
|
430
|
+
for tries in range(2):
|
431
|
+
sver = 0
|
409
432
|
try:
|
410
433
|
db = sqlite3.connect(db_path)
|
411
434
|
cur = db.cursor()
|
412
435
|
try:
|
436
|
+
zs = "select v from kv where k='sver'"
|
437
|
+
sver = cur.execute(zs).fetchall()[0][0]
|
438
|
+
if sver > VER_SESSION_DB:
|
439
|
+
zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
|
440
|
+
raise Exception(zs % (VER_SESSION_DB, sver))
|
441
|
+
|
413
442
|
cur.execute("select count(*) from us").fetchone()
|
414
|
-
create = False
|
415
|
-
break
|
416
443
|
except:
|
417
|
-
|
444
|
+
if sver:
|
445
|
+
raise
|
446
|
+
sver = 1
|
447
|
+
self._create_session_db(cur)
|
448
|
+
err = self._verify_session_db(cur, sver, db_path)
|
449
|
+
if err:
|
450
|
+
tries = 99
|
451
|
+
self.args.no_ses = True
|
452
|
+
self.log("root", err, 3)
|
453
|
+
break
|
454
|
+
|
418
455
|
except Exception as ex:
|
419
|
-
if
|
456
|
+
if tries or sver > VER_SESSION_DB:
|
420
457
|
raise
|
421
|
-
t = "sessions-db
|
458
|
+
t = "sessions-db is unusable; deleting and recreating: %r"
|
422
459
|
self.log("root", t % (ex,), 3)
|
423
460
|
try:
|
424
461
|
cur.close() # type: ignore
|
@@ -428,8 +465,13 @@ class SvcHub(object):
|
|
428
465
|
db.close() # type: ignore
|
429
466
|
except:
|
430
467
|
pass
|
468
|
+
try:
|
469
|
+
os.unlink(db_lock)
|
470
|
+
except:
|
471
|
+
pass
|
431
472
|
os.unlink(db_path)
|
432
473
|
|
474
|
+
def _create_session_db(self, cur ) :
|
433
475
|
sch = [
|
434
476
|
r"create table kv (k text, v int)",
|
435
477
|
r"create table us (un text, si text, t0 int)",
|
@@ -439,15 +481,44 @@ class SvcHub(object):
|
|
439
481
|
r"create index us_t0 on us(t0)",
|
440
482
|
r"insert into kv values ('sver', 1)",
|
441
483
|
]
|
484
|
+
for cmd in sch:
|
485
|
+
cur.execute(cmd)
|
486
|
+
self.log("root", "created new sessions-db")
|
442
487
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
488
|
+
def _verify_session_db(self, cur , sver , db_path ) :
|
489
|
+
# ensure writable (maybe owned by other user)
|
490
|
+
db = cur.connection
|
491
|
+
|
492
|
+
try:
|
493
|
+
zil = cur.execute("select v from kv where k='pid'").fetchall()
|
494
|
+
if len(zil) > 1:
|
495
|
+
raise Exception()
|
496
|
+
owner = zil[0][0]
|
497
|
+
except:
|
498
|
+
owner = 0
|
499
|
+
|
500
|
+
if not lock_file(db_path + ".lock"):
|
501
|
+
t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
|
502
|
+
return t % (db_path, owner)
|
503
|
+
|
504
|
+
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
|
505
|
+
if owner:
|
506
|
+
# wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
|
507
|
+
for k, v in vars:
|
508
|
+
cur.execute("update kv set v=? where k=?", (v, k))
|
509
|
+
else:
|
510
|
+
# wear-estimate: 3~4 cells; offsets 0x10, 0x50, 0x19180, 0x19710, 0x36000, 0x360b0, 0x36b90
|
511
|
+
for k, v in vars:
|
512
|
+
cur.execute("insert into kv values(?, ?)", (k, v))
|
448
513
|
|
514
|
+
if sver < VER_SESSION_DB:
|
515
|
+
cur.execute("delete from kv where k='sver'")
|
516
|
+
cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
|
517
|
+
|
518
|
+
db.commit()
|
449
519
|
cur.close()
|
450
520
|
db.close()
|
521
|
+
return ""
|
451
522
|
|
452
523
|
def setup_share_db(self) :
|
453
524
|
al = self.args
|
@@ -456,7 +527,6 @@ class SvcHub(object):
|
|
456
527
|
al.shr = ""
|
457
528
|
return
|
458
529
|
|
459
|
-
import sqlite3
|
460
530
|
|
461
531
|
al.shr = al.shr.strip("/")
|
462
532
|
if "/" in al.shr or not al.shr:
|
@@ -467,34 +537,48 @@ class SvcHub(object):
|
|
467
537
|
al.shr = "/%s/" % (al.shr,)
|
468
538
|
al.shr1 = al.shr[1:]
|
469
539
|
|
470
|
-
|
471
|
-
|
540
|
+
# policy:
|
541
|
+
# the shares-db is important, so panic if something is wrong
|
542
|
+
|
472
543
|
db_path = self.args.shr_db
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
544
|
+
db_lock = db_path + ".lock"
|
545
|
+
try:
|
546
|
+
create = not os.path.getsize(db_path)
|
547
|
+
except:
|
548
|
+
create = True
|
549
|
+
zs = "creating new" if create else "opening"
|
550
|
+
self.log("root", "%s shares-db %s" % (zs, db_path))
|
551
|
+
|
552
|
+
sver = 0
|
553
|
+
try:
|
554
|
+
db = sqlite3.connect(db_path)
|
555
|
+
cur = db.cursor()
|
556
|
+
if not create:
|
557
|
+
zs = "select v from kv where k='sver'"
|
558
|
+
sver = cur.execute(zs).fetchall()[0][0]
|
559
|
+
if sver > VER_SHARES_DB:
|
560
|
+
zs = "this version of copyparty only understands shares-db v%d and older; the db is v%d"
|
561
|
+
raise Exception(zs % (VER_SHARES_DB, sver))
|
562
|
+
|
563
|
+
cur.execute("select count(*) from sh").fetchone()
|
564
|
+
except Exception as ex:
|
565
|
+
t = "could not open shares-db; will now panic...\nthe following database must be repaired or deleted before you can launch copyparty:\n%s\n\nERROR: %s\n\nadditional details:\n%s\n"
|
566
|
+
self.log("root", t % (db_path, ex, min_ex()), 1)
|
567
|
+
raise
|
568
|
+
|
569
|
+
try:
|
570
|
+
zil = cur.execute("select v from kv where k='pid'").fetchall()
|
571
|
+
if len(zil) > 1:
|
572
|
+
raise Exception()
|
573
|
+
owner = zil[0][0]
|
574
|
+
except:
|
575
|
+
owner = 0
|
576
|
+
|
577
|
+
if not lock_file(db_lock):
|
578
|
+
t = "the shares-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --shr-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now panic."
|
579
|
+
t = t % (db_path, owner)
|
580
|
+
self.log("root", t, 1)
|
581
|
+
raise Exception(t)
|
498
582
|
|
499
583
|
sch1 = [
|
500
584
|
r"create table kv (k text, v int)",
|
@@ -506,32 +590,35 @@ class SvcHub(object):
|
|
506
590
|
r"create index sf_k on sf(k)",
|
507
591
|
r"create index sh_k on sh(k)",
|
508
592
|
r"create index sh_t1 on sh(t1)",
|
593
|
+
r"insert into kv values ('sver', 2)",
|
509
594
|
]
|
510
595
|
|
511
|
-
if
|
512
|
-
|
513
|
-
modified = True
|
596
|
+
if not sver:
|
597
|
+
sver = VER_SHARES_DB
|
514
598
|
for cmd in sch1 + sch2:
|
515
599
|
cur.execute(cmd)
|
516
600
|
self.log("root", "created new shares-db")
|
517
|
-
else:
|
518
|
-
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
519
601
|
|
520
|
-
if
|
521
|
-
modified = True
|
602
|
+
if sver == 1:
|
522
603
|
for cmd in sch2:
|
523
604
|
cur.execute(cmd)
|
524
605
|
cur.execute("update sh set st = 0")
|
525
606
|
self.log("root", "shares-db schema upgrade ok")
|
526
607
|
|
527
|
-
if
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
db
|
608
|
+
if sver < VER_SHARES_DB:
|
609
|
+
cur.execute("delete from kv where k='sver'")
|
610
|
+
cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
|
611
|
+
|
612
|
+
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
|
613
|
+
if owner:
|
614
|
+
# wear-estimate: same as sessions-db
|
615
|
+
for k, v in vars:
|
616
|
+
cur.execute("update kv set v=? where k=?", (v, k))
|
617
|
+
else:
|
618
|
+
for k, v in vars:
|
619
|
+
cur.execute("insert into kv values(?, ?)", (k, v))
|
534
620
|
|
621
|
+
db.commit()
|
535
622
|
cur.close()
|
536
623
|
db.close()
|
537
624
|
|
@@ -669,8 +756,8 @@ class SvcHub(object):
|
|
669
756
|
t += ", "
|
670
757
|
t += "\033[0mNG: \033[35m" + sng
|
671
758
|
|
672
|
-
t += "\033[0m, see --deps"
|
673
|
-
self.log("dependencies", t, 6)
|
759
|
+
t += "\033[0m, see --deps (this is fine btw)"
|
760
|
+
self.log("optional-dependencies", t, 6)
|
674
761
|
|
675
762
|
def _check_env(self) :
|
676
763
|
try:
|
@@ -753,13 +840,14 @@ class SvcHub(object):
|
|
753
840
|
vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
|
754
841
|
setattr(al, k, vl)
|
755
842
|
|
756
|
-
for k in "lo hist ssl_log".split(" "):
|
843
|
+
for k in "lo hist dbpath ssl_log".split(" "):
|
757
844
|
vs = getattr(al, k)
|
758
845
|
if vs:
|
759
846
|
vs = os.path.expandvars(os.path.expanduser(vs))
|
760
847
|
setattr(al, k, vs)
|
761
848
|
|
762
|
-
|
849
|
+
zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
|
850
|
+
for k in zs.split(" "):
|
763
851
|
vs = getattr(al, k)
|
764
852
|
if not vs or vs == "no":
|
765
853
|
setattr(al, k, None)
|
copyparty/szip.py
CHANGED
@@ -4,12 +4,11 @@ from __future__ import print_function, unicode_literals
|
|
4
4
|
import calendar
|
5
5
|
import stat
|
6
6
|
import time
|
7
|
-
import zlib
|
8
7
|
|
9
8
|
from .authsrv import AuthSrv
|
10
9
|
from .bos import bos
|
11
10
|
from .sutil import StreamArc, errdesc
|
12
|
-
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile
|
11
|
+
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile, zlib
|
13
12
|
|
14
13
|
def dostime2unix(buf ) :
|
15
14
|
t, d = sunpack(b"<HH", buf)
|
copyparty/tcpsrv.py
CHANGED
@@ -148,9 +148,15 @@ class TcpSrv(object):
|
|
148
148
|
if just_ll or self.args.ll:
|
149
149
|
ll_ok.add(ip.split("/")[0])
|
150
150
|
|
151
|
+
listening_on = []
|
152
|
+
for ip, ports in sorted(ok.items()):
|
153
|
+
for port in sorted(ports):
|
154
|
+
listening_on.append("%s %s" % (ip, port))
|
155
|
+
|
151
156
|
qr1 = {}
|
152
157
|
qr2 = {}
|
153
158
|
msgs = []
|
159
|
+
accessible_on = []
|
154
160
|
title_tab = {}
|
155
161
|
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
|
156
162
|
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
@@ -166,6 +172,10 @@ class TcpSrv(object):
|
|
166
172
|
):
|
167
173
|
continue
|
168
174
|
|
175
|
+
zs = "%s %s" % (ip, port)
|
176
|
+
if zs not in accessible_on:
|
177
|
+
accessible_on.append(zs)
|
178
|
+
|
169
179
|
proto = " http"
|
170
180
|
if self.args.http_only:
|
171
181
|
pass
|
@@ -216,6 +226,14 @@ class TcpSrv(object):
|
|
216
226
|
else:
|
217
227
|
print("\n", end="")
|
218
228
|
|
229
|
+
for fn, ls in (
|
230
|
+
(self.args.wr_h_eps, listening_on),
|
231
|
+
(self.args.wr_h_aon, accessible_on),
|
232
|
+
):
|
233
|
+
if fn:
|
234
|
+
with open(fn, "wb") as f:
|
235
|
+
f.write(("\n".join(ls)).encode("utf-8"))
|
236
|
+
|
219
237
|
if self.args.qr or self.args.qrs:
|
220
238
|
self.qr = self._qr(qr1, qr2)
|
221
239
|
|
copyparty/tftpd.py
CHANGED
@@ -36,7 +36,19 @@ from partftpy.TftpShared import TftpException
|
|
36
36
|
from .__init__ import EXE, PY2, TYPE_CHECKING
|
37
37
|
from .authsrv import VFS
|
38
38
|
from .bos import bos
|
39
|
-
from .util import
|
39
|
+
from .util import (
|
40
|
+
FN_EMB,
|
41
|
+
UTC,
|
42
|
+
BytesIO,
|
43
|
+
Daemon,
|
44
|
+
ODict,
|
45
|
+
exclude_dotfiles,
|
46
|
+
min_ex,
|
47
|
+
runhook,
|
48
|
+
undot,
|
49
|
+
vjoin,
|
50
|
+
vsplit,
|
51
|
+
)
|
40
52
|
|
41
53
|
if TYPE_CHECKING:
|
42
54
|
from .svchub import SvcHub
|
@@ -241,16 +253,25 @@ class Tftpd(object):
|
|
241
253
|
for srv in srvs:
|
242
254
|
srv.stop()
|
243
255
|
|
244
|
-
def _v2a(
|
256
|
+
def _v2a(
|
257
|
+
self, caller , vpath , perms , *a
|
258
|
+
) :
|
245
259
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
246
260
|
if not perms:
|
247
261
|
perms = [True, True]
|
248
262
|
|
249
263
|
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
|
250
264
|
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
265
|
+
if perms[1] and "*" not in vfs.axs.uread and "wo_up_readme" not in vfs.flags:
|
266
|
+
zs, fn = vsplit(vpath)
|
267
|
+
if fn.lower() in FN_EMB:
|
268
|
+
vpath = vjoin(zs, "_wo_" + fn)
|
269
|
+
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
270
|
+
|
251
271
|
if not vfs.realpath:
|
252
272
|
raise Exception("unmapped vfs")
|
253
|
-
|
273
|
+
|
274
|
+
return vfs, vpath, vfs.canonical(rem)
|
254
275
|
|
255
276
|
def _ls(self, vpath , raddress , rport , force=False) :
|
256
277
|
# generate file listing if vpath is dir.txt and return as file object
|
@@ -328,7 +349,7 @@ class Tftpd(object):
|
|
328
349
|
else:
|
329
350
|
raise Exception("bad mode %s" % (mode,))
|
330
351
|
|
331
|
-
vfs, ap = self._v2a("open", vpath, [rd, wr])
|
352
|
+
vfs, vpath, ap = self._v2a("open", vpath, [rd, wr])
|
332
353
|
if wr:
|
333
354
|
if "*" not in vfs.axs.uwrite:
|
334
355
|
yeet("blocked write; folder not world-writable: /%s" % (vpath,))
|
@@ -365,7 +386,7 @@ class Tftpd(object):
|
|
365
386
|
return open(ap, mode, *a, **ka)
|
366
387
|
|
367
388
|
def _mkdir(self, vpath , *a) :
|
368
|
-
vfs, ap = self._v2a("mkdir", vpath, [])
|
389
|
+
vfs, _, ap = self._v2a("mkdir", vpath, [False, True])
|
369
390
|
if "*" not in vfs.axs.uwrite:
|
370
391
|
yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
|
371
392
|
|
@@ -373,7 +394,7 @@ class Tftpd(object):
|
|
373
394
|
|
374
395
|
def _unlink(self, vpath ) :
|
375
396
|
# return bos.unlink(self._v2a("stat", vpath, *a)[1])
|
376
|
-
vfs, ap = self._v2a("delete", vpath, [True, False, False, True])
|
397
|
+
vfs, _, ap = self._v2a("delete", vpath, [True, False, False, True])
|
377
398
|
|
378
399
|
try:
|
379
400
|
inf = bos.stat(ap)
|
@@ -397,7 +418,7 @@ class Tftpd(object):
|
|
397
418
|
|
398
419
|
def _p_exists(self, vpath ) :
|
399
420
|
try:
|
400
|
-
ap = self._v2a("p.exists", vpath, [False, False])[
|
421
|
+
ap = self._v2a("p.exists", vpath, [False, False])[2]
|
401
422
|
bos.stat(ap)
|
402
423
|
return True
|
403
424
|
except:
|
@@ -405,7 +426,7 @@ class Tftpd(object):
|
|
405
426
|
|
406
427
|
def _p_isdir(self, vpath ) :
|
407
428
|
try:
|
408
|
-
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[
|
429
|
+
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[2])
|
409
430
|
ret = stat.S_ISDIR(st.st_mode)
|
410
431
|
return ret
|
411
432
|
except:
|
copyparty/th_cli.py
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
+
import errno
|
4
5
|
import os
|
6
|
+
import stat
|
5
7
|
|
6
8
|
from .__init__ import TYPE_CHECKING
|
7
9
|
from .authsrv import VFS
|
8
10
|
from .bos import bos
|
9
11
|
from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
|
10
|
-
from .util import Cooldown
|
12
|
+
from .util import Cooldown, Pebkac
|
11
13
|
|
12
14
|
if TYPE_CHECKING:
|
13
15
|
from .httpsrv import HttpSrv
|
14
16
|
|
15
17
|
|
18
|
+
IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r"
|
19
|
+
|
20
|
+
|
16
21
|
class ThumbCli(object):
|
17
22
|
def __init__(self, hsrv ) :
|
18
23
|
self.broker = hsrv.broker
|
@@ -121,7 +126,7 @@ class ThumbCli(object):
|
|
121
126
|
|
122
127
|
tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
|
123
128
|
tpaths = [tpath]
|
124
|
-
if fmt == "w":
|
129
|
+
if fmt[:1] == "w":
|
125
130
|
# also check for jpg (maybe webp is unavailable)
|
126
131
|
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
|
127
132
|
|
@@ -154,8 +159,22 @@ class ThumbCli(object):
|
|
154
159
|
if abort:
|
155
160
|
return None
|
156
161
|
|
157
|
-
|
158
|
-
|
162
|
+
ap = os.path.join(ptop, rem)
|
163
|
+
try:
|
164
|
+
st = bos.stat(ap)
|
165
|
+
if not st.st_size or not stat.S_ISREG(st.st_mode):
|
166
|
+
return None
|
167
|
+
|
168
|
+
with open(ap, "rb", 4) as f:
|
169
|
+
if not f.read(4):
|
170
|
+
raise Exception()
|
171
|
+
except OSError as ex:
|
172
|
+
if ex.errno == errno.ENOENT:
|
173
|
+
raise Pebkac(404)
|
174
|
+
else:
|
175
|
+
raise Pebkac(500, IOERROR % (ex,))
|
176
|
+
except Exception as ex:
|
177
|
+
raise Pebkac(500, IOERROR % (ex,))
|
159
178
|
|
160
179
|
x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt)
|
161
180
|
return x.get() # type: ignore
|
copyparty/th_srv.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|
4
4
|
import hashlib
|
5
5
|
import logging
|
6
6
|
import os
|
7
|
+
import re
|
7
8
|
import shutil
|
8
9
|
import subprocess as sp
|
9
10
|
import threading
|
@@ -46,6 +47,9 @@ HAVE_WEBP = False
|
|
46
47
|
EXTS_TH = set(["jpg", "webp", "png"])
|
47
48
|
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
48
49
|
|
50
|
+
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
51
|
+
|
52
|
+
|
49
53
|
try:
|
50
54
|
if os.environ.get("PRTY_NO_PIL"):
|
51
55
|
raise Exception()
|
@@ -382,8 +386,12 @@ class ThumbSrv(object):
|
|
382
386
|
self.log(msg, c)
|
383
387
|
if getattr(ex, "returncode", 0) != 321:
|
384
388
|
if fun == funs[-1]:
|
385
|
-
|
386
|
-
|
389
|
+
try:
|
390
|
+
with open(ttpath, "wb") as _:
|
391
|
+
pass
|
392
|
+
except Exception as ex:
|
393
|
+
t = "failed to create the file [%s]: %r"
|
394
|
+
self.log(t % (ttpath, ex), 3)
|
387
395
|
else:
|
388
396
|
# ffmpeg may spawn empty files on windows
|
389
397
|
try:
|
@@ -396,7 +404,10 @@ class ThumbSrv(object):
|
|
396
404
|
|
397
405
|
try:
|
398
406
|
wrename(self.log, ttpath, tpath, vn.flags)
|
399
|
-
except:
|
407
|
+
except Exception as ex:
|
408
|
+
if not os.path.exists(tpath):
|
409
|
+
t = "failed to move [%s] to [%s]: %r"
|
410
|
+
self.log(t % (ttpath, tpath, ex), 3)
|
400
411
|
pass
|
401
412
|
|
402
413
|
with self.mutex:
|
@@ -987,6 +998,8 @@ class ThumbSrv(object):
|
|
987
998
|
# thumb file
|
988
999
|
try:
|
989
1000
|
b64, ts, ext = f.split(".")
|
1001
|
+
if len(ts) > 8 and PTN_TS.match(ts):
|
1002
|
+
ts = "yeahokay"
|
990
1003
|
if len(b64) != 24 or len(ts) != 8 or ext not in exts:
|
991
1004
|
raise Exception()
|
992
1005
|
except:
|
copyparty/u2idx.py
CHANGED
@@ -128,9 +128,9 @@ class U2idx(object):
|
|
128
128
|
|
129
129
|
|
130
130
|
ptop = vn.realpath
|
131
|
-
histpath = self.asrv.vfs.
|
131
|
+
histpath = self.asrv.vfs.dbpaths.get(ptop)
|
132
132
|
if not histpath:
|
133
|
-
self.log("no
|
133
|
+
self.log("no dbpath for %r" % (ptop,))
|
134
134
|
return None
|
135
135
|
|
136
136
|
db_path = os.path.join(histpath, "up2k.db")
|