copyparty 1.18.9__py3-none-any.whl → 1.19.0__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 +0 -4
- copyparty/__main__.py +85 -69
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +31 -2
- copyparty/cfg.py +2 -0
- copyparty/ftpd.py +7 -2
- copyparty/httpcli.py +87 -31
- copyparty/httpsrv.py +2 -1
- copyparty/mtag.py +5 -1
- copyparty/multicast.py +1 -5
- copyparty/pwhash.py +4 -0
- copyparty/svchub.py +2 -11
- copyparty/tcpsrv.py +16 -6
- copyparty/tftpd.py +1 -1
- copyparty/th_cli.py +2 -2
- copyparty/th_srv.py +68 -2
- copyparty/up2k.py +21 -18
- copyparty/util.py +11 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/mde.js.gz +0 -0
- copyparty/web/rups.html +1 -8
- copyparty/web/rups.js.gz +0 -0
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.html +6 -1
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -0
- copyparty/web/svcs.js.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/METADATA +50 -9
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/RECORD +37 -42
- copyparty/web/dd/2.png +0 -0
- copyparty/web/dd/3.png +0 -0
- copyparty/web/dd/4.png +0 -0
- copyparty/web/dd/5.png +0 -0
- copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/WHEEL +0 -0
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/entry_points.txt +0 -0
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.18.9.dist-info → copyparty-1.19.0.dist-info}/top_level.txt +0 -0
copyparty/cfg.py
CHANGED
@@ -111,6 +111,7 @@ def vf_vmap() :
|
|
111
111
|
"tail_tmax",
|
112
112
|
"tail_who",
|
113
113
|
"tcolor",
|
114
|
+
"txt_eol",
|
114
115
|
"unlist",
|
115
116
|
"u2abort",
|
116
117
|
"u2ts",
|
@@ -322,6 +323,7 @@ flagcats = {
|
|
322
323
|
"exp": "enable textfile expansion; see --help-exp",
|
323
324
|
"exp_md": "placeholders to expand in markdown files; see --help",
|
324
325
|
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
|
326
|
+
"txt_eol=lf": "enable EOL conversion when writing docs (LF or CRLF)",
|
325
327
|
},
|
326
328
|
"tailing": {
|
327
329
|
"notail": "disable ?tail (download a growing file continuously)",
|
copyparty/ftpd.py
CHANGED
@@ -79,7 +79,12 @@ class FtpAuth(DummyAuthorizer):
|
|
79
79
|
uname = "*"
|
80
80
|
if username != "anonymous":
|
81
81
|
uname = ""
|
82
|
-
|
82
|
+
if args.usernames:
|
83
|
+
alts = ["%s:%s" % (username, password)]
|
84
|
+
else:
|
85
|
+
alts = password, username
|
86
|
+
|
87
|
+
for zs in alts:
|
83
88
|
zs = asrv.iacct.get(asrv.ah.hash(zs), "")
|
84
89
|
if zs:
|
85
90
|
uname = zs
|
@@ -603,7 +608,7 @@ class Ftpd(object):
|
|
603
608
|
if "::" in ips:
|
604
609
|
ips.append("0.0.0.0")
|
605
610
|
|
606
|
-
ips = [x for x in ips if "unix:"
|
611
|
+
ips = [x for x in ips if not x.startswith(("unix:", "fd:"))]
|
607
612
|
|
608
613
|
if self.args.ftp4:
|
609
614
|
ips = [x for x in ips if ":" not in x]
|
copyparty/httpcli.py
CHANGED
@@ -62,6 +62,7 @@ from .util import (
|
|
62
62
|
alltrace,
|
63
63
|
atomic_move,
|
64
64
|
b64dec,
|
65
|
+
eol_conv,
|
65
66
|
exclude_dotfiles,
|
66
67
|
formatdate,
|
67
68
|
fsenc,
|
@@ -257,7 +258,8 @@ class HttpCli(object):
|
|
257
258
|
|
258
259
|
def _assert_safe_rem(self, rem ) :
|
259
260
|
# sanity check to prevent any disasters
|
260
|
-
|
261
|
+
# (this function hopefully serves no purpose; validation has already happened at this point, this only exists as a last-ditch effort just in case)
|
262
|
+
if rem.startswith(("/", "../")) or "/../" in rem:
|
261
263
|
raise Exception("that was close")
|
262
264
|
|
263
265
|
def _gen_fk(self, alg , salt , fspath , fsize , inode ) :
|
@@ -378,9 +380,20 @@ class HttpCli(object):
|
|
378
380
|
try:
|
379
381
|
cli_ip = zsl[n].strip()
|
380
382
|
except:
|
381
|
-
cli_ip =
|
382
|
-
|
383
|
-
self.
|
383
|
+
cli_ip = self.ip
|
384
|
+
self.bad_xff = True
|
385
|
+
if self.args.rproxy != 9999999:
|
386
|
+
t = "global-option --rproxy %d could not be used (out-of-bounds) for the received header [%s]"
|
387
|
+
self.log(t % (self.args.rproxy, zso), c=3)
|
388
|
+
else:
|
389
|
+
zsl = [
|
390
|
+
" rproxy: %d if this client's IP-address is [%s]"
|
391
|
+
% (-1 - zd, zs.strip())
|
392
|
+
for zd, zs in enumerate(zsl)
|
393
|
+
]
|
394
|
+
t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:'
|
395
|
+
t = t % (self.args.xff_hdr,)
|
396
|
+
self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3)
|
384
397
|
|
385
398
|
pip = self.conn.addr[0]
|
386
399
|
xffs = self.conn.xff_nm
|
@@ -653,6 +666,9 @@ class HttpCli(object):
|
|
653
666
|
self.pw = ""
|
654
667
|
self.uname = idp_usr
|
655
668
|
self.html_head += "<script>var is_idp=1</script>\n"
|
669
|
+
zs = self.asrv.ases.get(idp_usr)
|
670
|
+
if zs:
|
671
|
+
self.set_idp_cookie(zs)
|
656
672
|
else:
|
657
673
|
self.log("unknown username: %r" % (idp_usr,), 1)
|
658
674
|
|
@@ -1191,15 +1207,6 @@ class HttpCli(object):
|
|
1191
1207
|
self.reply(b"ssdp is disabled in server config", 404)
|
1192
1208
|
return False
|
1193
1209
|
|
1194
|
-
if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
|
1195
|
-
if self.args.mpmc == ".":
|
1196
|
-
raise Pebkac(404)
|
1197
|
-
|
1198
|
-
loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
|
1199
|
-
h = {"Location": loc, "Cache-Control": "max-age=39"}
|
1200
|
-
self.reply(b"", 301, headers=h)
|
1201
|
-
return True
|
1202
|
-
|
1203
1210
|
if self.vpath == ".cpr/metrics":
|
1204
1211
|
return self.conn.hsrv.metrics.tx(self)
|
1205
1212
|
|
@@ -2075,16 +2082,16 @@ class HttpCli(object):
|
|
2075
2082
|
rnd, lifetime, xbu, xau = self.upload_flags(vfs)
|
2076
2083
|
lim = vfs.get_dbv(rem)[0].lim
|
2077
2084
|
fdir = vfs.canonical(rem)
|
2078
|
-
if lim:
|
2079
|
-
fdir, rem = lim.all(
|
2080
|
-
self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
|
2081
|
-
)
|
2082
|
-
|
2083
2085
|
fn = None
|
2084
2086
|
if rem and not self.trailing_slash and not bos.path.isdir(fdir):
|
2085
2087
|
fdir, fn = os.path.split(fdir)
|
2086
2088
|
rem, _ = vsplit(rem)
|
2087
2089
|
|
2090
|
+
if lim:
|
2091
|
+
fdir, rem = lim.all(
|
2092
|
+
self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
|
2093
|
+
)
|
2094
|
+
|
2088
2095
|
bos.makedirs(fdir, vf=vfs.flags)
|
2089
2096
|
|
2090
2097
|
open_ka = {"fun": open}
|
@@ -2918,12 +2925,16 @@ class HttpCli(object):
|
|
2918
2925
|
return True
|
2919
2926
|
|
2920
2927
|
def handle_chpw(self) :
|
2928
|
+
if self.args.usernames:
|
2929
|
+
self.parser.require("uname", 64)
|
2921
2930
|
pwd = self.parser.require("pw", 64)
|
2922
2931
|
self.parser.drop()
|
2923
2932
|
|
2924
2933
|
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
|
2925
2934
|
if ok:
|
2926
2935
|
self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes")
|
2936
|
+
if self.args.usernames:
|
2937
|
+
pwd = "%s:%s" % (self.uname, pwd)
|
2927
2938
|
ok, msg = self.get_pwd_cookie(pwd)
|
2928
2939
|
if ok:
|
2929
2940
|
msg = "new password OK"
|
@@ -2935,6 +2946,15 @@ class HttpCli(object):
|
|
2935
2946
|
return True
|
2936
2947
|
|
2937
2948
|
def handle_login(self) :
|
2949
|
+
if self.args.usernames and not (
|
2950
|
+
self.args.shr and self.vpath.startswith(self.args.shr1)
|
2951
|
+
):
|
2952
|
+
try:
|
2953
|
+
un = self.parser.require("uname", 64)
|
2954
|
+
except:
|
2955
|
+
un = ""
|
2956
|
+
else:
|
2957
|
+
un = ""
|
2938
2958
|
pwd = self.parser.require("cppwd", 64)
|
2939
2959
|
try:
|
2940
2960
|
uhash = self.parser.require("uhash", 256)
|
@@ -2945,6 +2965,9 @@ class HttpCli(object):
|
|
2945
2965
|
if not pwd:
|
2946
2966
|
raise Pebkac(422, "password cannot be blank")
|
2947
2967
|
|
2968
|
+
if un:
|
2969
|
+
pwd = "%s:%s" % (un, pwd)
|
2970
|
+
|
2948
2971
|
dst = self.args.SRS
|
2949
2972
|
if self.vpath:
|
2950
2973
|
dst += quotep(self.vpaths)
|
@@ -3024,6 +3047,19 @@ class HttpCli(object):
|
|
3024
3047
|
|
3025
3048
|
return dur > 0, msg
|
3026
3049
|
|
3050
|
+
def set_idp_cookie(self, ases) :
|
3051
|
+
k = "cppws" if self.is_https else "cppwd"
|
3052
|
+
ck = gencookie(
|
3053
|
+
k,
|
3054
|
+
ases,
|
3055
|
+
self.args.R,
|
3056
|
+
self.args.cookie_lax,
|
3057
|
+
self.is_https,
|
3058
|
+
self.args.idp_cookie,
|
3059
|
+
"; HttpOnly",
|
3060
|
+
)
|
3061
|
+
self.out_headers["Set-Cookie"] = ck
|
3062
|
+
|
3027
3063
|
def handle_mkdir(self) :
|
3028
3064
|
new_dir = self.parser.require("name", 512)
|
3029
3065
|
self.parser.drop()
|
@@ -3559,7 +3595,7 @@ class HttpCli(object):
|
|
3559
3595
|
rem = "{}/{}".format(rp, fn).strip("/")
|
3560
3596
|
dbv, vrem = vfs.get_dbv(rem)
|
3561
3597
|
|
3562
|
-
if not rem.endswith(".md") and not self.can_delete:
|
3598
|
+
if not rem.lower().endswith(".md") and not self.can_delete:
|
3563
3599
|
raise Pebkac(400, "only markdown pls")
|
3564
3600
|
|
3565
3601
|
if nullwrite:
|
@@ -3642,6 +3678,9 @@ class HttpCli(object):
|
|
3642
3678
|
if p_field != "body":
|
3643
3679
|
raise Pebkac(400, "expected body, got {}".format(p_field))
|
3644
3680
|
|
3681
|
+
if "txt_eol" in vfs.flags:
|
3682
|
+
p_data = eol_conv(p_data, vfs.flags["txt_eol"])
|
3683
|
+
|
3645
3684
|
xbu = vfs.flags.get("xbu")
|
3646
3685
|
if xbu:
|
3647
3686
|
if not runhook(
|
@@ -4608,7 +4647,9 @@ class HttpCli(object):
|
|
4608
4647
|
else:
|
4609
4648
|
fn = self.host.split(":")[0]
|
4610
4649
|
|
4611
|
-
if vn.flags.get("zipmax") and
|
4650
|
+
if vn.flags.get("zipmax") and not (
|
4651
|
+
vn.flags.get("zipmaxu") and self.uname != "*"
|
4652
|
+
):
|
4612
4653
|
maxs = vn.flags.get("zipmaxs_v") or 0
|
4613
4654
|
maxn = vn.flags.get("zipmaxn_v") or 0
|
4614
4655
|
nf = 0
|
@@ -4662,7 +4703,7 @@ class HttpCli(object):
|
|
4662
4703
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
4663
4704
|
cfmt = ""
|
4664
4705
|
if self.thumbcli and not self.args.no_bacode:
|
4665
|
-
for zs in ("opus", "mp3", "w", "j", "p"):
|
4706
|
+
for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"):
|
4666
4707
|
if zs in self.ouparam or uarg == zs:
|
4667
4708
|
cfmt = zs
|
4668
4709
|
|
@@ -5001,7 +5042,7 @@ class HttpCli(object):
|
|
5001
5042
|
wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
|
5002
5043
|
|
5003
5044
|
fmt = self.uparam.get("ls", "")
|
5004
|
-
if not fmt and
|
5045
|
+
if not fmt and self.ua.startswith(("curl/", "fetch")):
|
5005
5046
|
fmt = "v"
|
5006
5047
|
|
5007
5048
|
if fmt in ["v", "t", "txt"]:
|
@@ -5041,6 +5082,13 @@ class HttpCli(object):
|
|
5041
5082
|
self.reply(zb, mime="text/plain; charset=utf-8")
|
5042
5083
|
return True
|
5043
5084
|
|
5085
|
+
re_btn = ""
|
5086
|
+
nre = self.args.ctl_re
|
5087
|
+
if "re" in self.uparam:
|
5088
|
+
self.out_headers["Refresh"] = str(nre)
|
5089
|
+
elif nre:
|
5090
|
+
re_btn = "&re=%s" % (nre,)
|
5091
|
+
|
5044
5092
|
html = self.j2s(
|
5045
5093
|
"splash",
|
5046
5094
|
this=self,
|
@@ -5058,6 +5106,7 @@ class HttpCli(object):
|
|
5058
5106
|
mtpq=vs["mtpq"],
|
5059
5107
|
dbwt=vs["dbwt"],
|
5060
5108
|
url_suf=suf,
|
5109
|
+
re=re_btn,
|
5061
5110
|
k304=self.k304(),
|
5062
5111
|
no304=self.no304(),
|
5063
5112
|
k304vis=self.args.k304 > 0,
|
@@ -5103,7 +5152,7 @@ class HttpCli(object):
|
|
5103
5152
|
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>'
|
5104
5153
|
pt = "404 not found ┐( ´ -`)┌"
|
5105
5154
|
|
5106
|
-
if self.ua.startswith("curl/"
|
5155
|
+
if self.ua.startswith(("curl/", "fetch")):
|
5107
5156
|
pt = "# acct: %s\n%s\n" % (self.uname, pt)
|
5108
5157
|
self.reply(pt.encode("utf-8"), status=rc)
|
5109
5158
|
return True
|
@@ -5417,6 +5466,8 @@ class HttpCli(object):
|
|
5417
5466
|
elif nfi == 3:
|
5418
5467
|
if not vp.endswith(vfi):
|
5419
5468
|
continue
|
5469
|
+
else:
|
5470
|
+
continue
|
5420
5471
|
|
5421
5472
|
n -= 1
|
5422
5473
|
if not n:
|
@@ -5540,6 +5591,8 @@ class HttpCli(object):
|
|
5540
5591
|
elif nfi == 3:
|
5541
5592
|
if not vp.endswith(vfi):
|
5542
5593
|
continue
|
5594
|
+
else:
|
5595
|
+
continue
|
5543
5596
|
|
5544
5597
|
if not dots and "/." in vp:
|
5545
5598
|
continue
|
@@ -5970,6 +6023,12 @@ class HttpCli(object):
|
|
5970
6023
|
else:
|
5971
6024
|
[x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
|
5972
6025
|
|
6026
|
+
# nonce (tlnote: norwegian for flake as in snowflake)
|
6027
|
+
if self.args.no_fnugg:
|
6028
|
+
ls["fnugg"] = "nei"
|
6029
|
+
elif "fnugg" in self.headers:
|
6030
|
+
ls["fnugg"] = self.headers["fnugg"]
|
6031
|
+
|
5973
6032
|
ret = json.dumps(ls)
|
5974
6033
|
mime = "application/json"
|
5975
6034
|
|
@@ -6152,7 +6211,8 @@ class HttpCli(object):
|
|
6152
6211
|
if not use_filekey:
|
6153
6212
|
return self.tx_404(True)
|
6154
6213
|
|
6155
|
-
|
6214
|
+
is_md = abspath.lower().endswith(".md")
|
6215
|
+
if add_og and not is_md:
|
6156
6216
|
if og_ua or self.host not in self.headers.get("referer", ""):
|
6157
6217
|
self.vpath, og_fn = vsplit(self.vpath)
|
6158
6218
|
vpath = self.vpath
|
@@ -6164,10 +6224,10 @@ class HttpCli(object):
|
|
6164
6224
|
vpnodes.pop()
|
6165
6225
|
|
6166
6226
|
if (
|
6167
|
-
(
|
6227
|
+
(is_md or self.can_delete)
|
6168
6228
|
and "nohtml" not in vn.flags
|
6169
6229
|
and (
|
6170
|
-
("v" in self.uparam
|
6230
|
+
(is_md and "v" in self.uparam)
|
6171
6231
|
or "edit" in self.uparam
|
6172
6232
|
or "edit2" in self.uparam
|
6173
6233
|
)
|
@@ -6224,11 +6284,7 @@ class HttpCli(object):
|
|
6224
6284
|
is_ls = "ls" in self.uparam
|
6225
6285
|
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
6226
6286
|
|
6227
|
-
if (
|
6228
|
-
not is_ls
|
6229
|
-
and not add_og
|
6230
|
-
and (self.ua.startswith("curl/") or self.ua.startswith("fetch"))
|
6231
|
-
):
|
6287
|
+
if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")):
|
6232
6288
|
self.uparam["ls"] = "v"
|
6233
6289
|
is_ls = True
|
6234
6290
|
|
copyparty/httpsrv.py
CHANGED
@@ -319,7 +319,8 @@ class HttpSrv(object):
|
|
319
319
|
spins = 0
|
320
320
|
while self.ncli >= self.nclimax:
|
321
321
|
if not spins:
|
322
|
-
|
322
|
+
t = "at connection limit (global-option 'nc'); waiting"
|
323
|
+
self.log(self.name, t, 3)
|
323
324
|
|
324
325
|
spins += 1
|
325
326
|
time.sleep(0.1)
|
copyparty/mtag.py
CHANGED
@@ -61,6 +61,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
|
61
61
|
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
|
62
62
|
CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
|
63
63
|
|
64
|
+
FMT_AU = set("mp3 ogg flac wav".split())
|
65
|
+
|
64
66
|
|
65
67
|
class MParser(object):
|
66
68
|
def __init__(self, cmdline ) :
|
@@ -236,7 +238,7 @@ def parse_ffprobe(txt ) :
|
|
236
238
|
ret = {} # processed
|
237
239
|
md = {} # raw tags
|
238
240
|
|
239
|
-
is_audio = fmt.get("format_name") in
|
241
|
+
is_audio = fmt.get("format_name") in FMT_AU
|
240
242
|
if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
|
241
243
|
is_audio = True
|
242
244
|
|
@@ -264,6 +266,8 @@ def parse_ffprobe(txt ) :
|
|
264
266
|
["channel_layout", "chs"],
|
265
267
|
["sample_rate", ".hz"],
|
266
268
|
["bit_rate", ".aq"],
|
269
|
+
["bits_per_sample", ".bps"],
|
270
|
+
["bits_per_raw_sample", ".bprs"],
|
267
271
|
["duration", ".dur"],
|
268
272
|
]
|
269
273
|
|
copyparty/multicast.py
CHANGED
@@ -180,11 +180,7 @@ class MCast(object):
|
|
180
180
|
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
|
181
181
|
|
182
182
|
# gvfs breaks if a linklocal ip appears in a dns reply
|
183
|
-
ll = {
|
184
|
-
k: v
|
185
|
-
for k, v in srv.ips.items()
|
186
|
-
if k.startswith("169.254") or k.startswith("fe80")
|
187
|
-
}
|
183
|
+
ll = {k: v for k, v in srv.ips.items() if k.startswith(("169.254", "fe80"))}
|
188
184
|
rt = {k: v for k, v in srv.ips.items() if k not in ll}
|
189
185
|
|
190
186
|
if self.args.ll or not rt:
|
copyparty/pwhash.py
CHANGED
@@ -147,6 +147,10 @@ class PWHash(object):
|
|
147
147
|
def cli(self) :
|
148
148
|
import getpass
|
149
149
|
|
150
|
+
if self.args.usernames:
|
151
|
+
t = "since you have enabled --usernames, please provide username:password"
|
152
|
+
print(t)
|
153
|
+
|
150
154
|
while True:
|
151
155
|
try:
|
152
156
|
p1 = getpass.getpass("password> ")
|
copyparty/svchub.py
CHANGED
@@ -840,15 +840,6 @@ class SvcHub(object):
|
|
840
840
|
|
841
841
|
def _check_env(self) :
|
842
842
|
al = self.args
|
843
|
-
try:
|
844
|
-
files = os.listdir(E.cfg)
|
845
|
-
except:
|
846
|
-
files = []
|
847
|
-
|
848
|
-
hits = [x for x in files if x.lower().endswith(".conf")]
|
849
|
-
if hits:
|
850
|
-
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
|
851
|
-
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
|
852
843
|
|
853
844
|
if self.args.no_bauth:
|
854
845
|
t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead"
|
@@ -858,7 +849,7 @@ class SvcHub(object):
|
|
858
849
|
|
859
850
|
have_tcp = False
|
860
851
|
for zs in al.i:
|
861
|
-
if not zs.startswith("unix:"):
|
852
|
+
if not zs.startswith(("unix:", "fd:")):
|
862
853
|
have_tcp = True
|
863
854
|
if not have_tcp:
|
864
855
|
zb = False
|
@@ -868,7 +859,7 @@ class SvcHub(object):
|
|
868
859
|
setattr(al, zs, False)
|
869
860
|
zb = True
|
870
861
|
if zb:
|
871
|
-
t = "
|
862
|
+
t = "not listening on any ip-addresses (only unix-sockets and/or FDs); cannot enable zeroconf/mdns/ssdp as requested"
|
872
863
|
self.log("root", t, 3)
|
873
864
|
|
874
865
|
if not self.args.no_dav:
|
copyparty/tcpsrv.py
CHANGED
@@ -242,8 +242,10 @@ class TcpSrv(object):
|
|
242
242
|
|
243
243
|
def _listen(self, ip , port ) :
|
244
244
|
uds_perm = uds_gid = -1
|
245
|
+
bound = None
|
246
|
+
tcp = False
|
247
|
+
|
245
248
|
if "unix:" in ip:
|
246
|
-
tcp = False
|
247
249
|
ipv = socket.AF_UNIX
|
248
250
|
uds = ip.split(":")
|
249
251
|
ip = uds[-1]
|
@@ -256,7 +258,12 @@ class TcpSrv(object):
|
|
256
258
|
import grp
|
257
259
|
|
258
260
|
uds_gid = grp.getgrnam(uds[2]).gr_gid
|
261
|
+
elif "fd:" in ip:
|
262
|
+
fd = ip[3:]
|
263
|
+
bound = socket.socket(fileno=int(fd))
|
259
264
|
|
265
|
+
tcp = bound.proto == socket.IPPROTO_TCP
|
266
|
+
ipv = bound.family
|
260
267
|
elif ":" in ip:
|
261
268
|
tcp = True
|
262
269
|
ipv = socket.AF_INET6
|
@@ -264,7 +271,7 @@ class TcpSrv(object):
|
|
264
271
|
tcp = True
|
265
272
|
ipv = socket.AF_INET
|
266
273
|
|
267
|
-
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
274
|
+
srv = bound or socket.socket(ipv, socket.SOCK_STREAM)
|
268
275
|
|
269
276
|
if not ANYWIN or self.args.reuseaddr:
|
270
277
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
@@ -282,6 +289,10 @@ class TcpSrv(object):
|
|
282
289
|
if getattr(self.args, "freebind", False):
|
283
290
|
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
|
284
291
|
|
292
|
+
if bound:
|
293
|
+
self.srv.append(srv)
|
294
|
+
return
|
295
|
+
|
285
296
|
try:
|
286
297
|
if tcp:
|
287
298
|
srv.bind((ip, port))
|
@@ -434,7 +445,7 @@ class TcpSrv(object):
|
|
434
445
|
def detect_interfaces(self, listen_ips ) :
|
435
446
|
from .stolen.ifaddr import get_adapters
|
436
447
|
|
437
|
-
listen_ips = [x for x in listen_ips if "unix:"
|
448
|
+
listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
|
438
449
|
|
439
450
|
nics = get_adapters(True)
|
440
451
|
eps = {}
|
@@ -580,8 +591,7 @@ class TcpSrv(object):
|
|
580
591
|
if not ip:
|
581
592
|
return ""
|
582
593
|
|
583
|
-
if ":" in ip
|
584
|
-
ip = "[{}]".format(ip)
|
594
|
+
hip = "[%s]" % (ip,) if ":" in ip else ip
|
585
595
|
|
586
596
|
if self.args.http_only:
|
587
597
|
https = ""
|
@@ -593,7 +603,7 @@ class TcpSrv(object):
|
|
593
603
|
ports = t1.get(ip, t2.get(ip, []))
|
594
604
|
dport = 443 if https else 80
|
595
605
|
port = "" if dport in ports or not ports else ":{}".format(ports[0])
|
596
|
-
txt = "http{}://{}{}/{}".format(https,
|
606
|
+
txt = "http{}://{}{}/{}".format(https, hip, port, self.args.qrl)
|
597
607
|
|
598
608
|
btxt = txt.encode("utf-8")
|
599
609
|
if PY2:
|
copyparty/tftpd.py
CHANGED
@@ -176,7 +176,7 @@ class Tftpd(object):
|
|
176
176
|
if "::" in ips:
|
177
177
|
ips.append("0.0.0.0")
|
178
178
|
|
179
|
-
ips = [x for x in ips if "unix:"
|
179
|
+
ips = [x for x in ips if not x.startswith(("unix:", "fd:"))]
|
180
180
|
|
181
181
|
if self.args.tftp4:
|
182
182
|
ips = [x for x in ips if ":" not in x]
|
copyparty/th_cli.py
CHANGED
@@ -85,7 +85,7 @@ class ThumbCli(object):
|
|
85
85
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
86
86
|
return os.path.join(ptop, rem)
|
87
87
|
|
88
|
-
if fmt[:1] in "jw":
|
88
|
+
if fmt[:1] in "jw" and fmt != "wav":
|
89
89
|
sfmt = fmt[:1]
|
90
90
|
|
91
91
|
if sfmt == "j" and self.args.th_no_jpg:
|
@@ -126,7 +126,7 @@ class ThumbCli(object):
|
|
126
126
|
|
127
127
|
tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
|
128
128
|
tpaths = [tpath]
|
129
|
-
if fmt[:1] == "w":
|
129
|
+
if fmt[:1] == "w" and fmt != "wav":
|
130
130
|
# also check for jpg (maybe webp is unavailable)
|
131
131
|
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
|
132
132
|
|
copyparty/th_srv.py
CHANGED
@@ -47,7 +47,7 @@ HAVE_AVIF = False
|
|
47
47
|
HAVE_WEBP = False
|
48
48
|
|
49
49
|
EXTS_TH = set(["jpg", "webp", "png"])
|
50
|
-
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
50
|
+
EXTS_AC = set(["opus", "owa", "caf", "mp3", "flac", "wav"])
|
51
51
|
EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
|
52
52
|
|
53
53
|
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
@@ -352,8 +352,10 @@ class ThumbSrv(object):
|
|
352
352
|
tex = tpath.rsplit(".", 1)[-1]
|
353
353
|
want_mp3 = tex == "mp3"
|
354
354
|
want_opus = tex in ("opus", "owa", "caf")
|
355
|
+
want_flac = tex == "flac"
|
356
|
+
want_wav = tex == "wav"
|
355
357
|
want_png = tex == "png"
|
356
|
-
want_au = want_mp3 or want_opus
|
358
|
+
want_au = want_mp3 or want_opus or want_flac or want_wav
|
357
359
|
for lib in self.args.th_dec:
|
358
360
|
can_au = lib == "ff" and (
|
359
361
|
ext in self.fmt_ffa or ext in self.fmt_ffv
|
@@ -368,6 +370,10 @@ class ThumbSrv(object):
|
|
368
370
|
funs.append(self.conv_opus)
|
369
371
|
elif want_mp3:
|
370
372
|
funs.append(self.conv_mp3)
|
373
|
+
elif want_flac:
|
374
|
+
funs.append(self.conv_flac)
|
375
|
+
elif want_wav:
|
376
|
+
funs.append(self.conv_wav)
|
371
377
|
elif want_png:
|
372
378
|
funs.append(self.conv_waves)
|
373
379
|
png_ok = True
|
@@ -803,6 +809,66 @@ class ThumbSrv(object):
|
|
803
809
|
# fmt: on
|
804
810
|
self._run_ff(cmd, vn, oom=300)
|
805
811
|
|
812
|
+
def conv_flac(self, abspath , tpath , fmt , vn ) :
|
813
|
+
if self.args.no_acode or not self.args.allow_flac:
|
814
|
+
raise Exception("flac not permitted in server config")
|
815
|
+
|
816
|
+
self.wait4ram(0.2, tpath)
|
817
|
+
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
818
|
+
if "ac" not in tags:
|
819
|
+
raise Exception("not audio")
|
820
|
+
|
821
|
+
self.log("conv2 flac", 6)
|
822
|
+
|
823
|
+
# fmt: off
|
824
|
+
cmd = [
|
825
|
+
b"ffmpeg",
|
826
|
+
b"-nostdin",
|
827
|
+
b"-v", b"error",
|
828
|
+
b"-hide_banner",
|
829
|
+
b"-i", fsenc(abspath),
|
830
|
+
b"-map", b"0:a:0",
|
831
|
+
b"-c:a", b"flac",
|
832
|
+
fsenc(tpath)
|
833
|
+
]
|
834
|
+
# fmt: on
|
835
|
+
self._run_ff(cmd, vn, oom=300)
|
836
|
+
|
837
|
+
def conv_wav(self, abspath , tpath , fmt , vn ) :
|
838
|
+
if self.args.no_acode or not self.args.allow_wav:
|
839
|
+
raise Exception("wav not permitted in server config")
|
840
|
+
|
841
|
+
self.wait4ram(0.2, tpath)
|
842
|
+
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
843
|
+
if "ac" not in tags:
|
844
|
+
raise Exception("not audio")
|
845
|
+
|
846
|
+
bits = tags[".bps"][1]
|
847
|
+
if bits == 0.0:
|
848
|
+
bits = tags[".bprs"][1]
|
849
|
+
|
850
|
+
codec = b"pcm_s32le"
|
851
|
+
if bits <= 16.0:
|
852
|
+
codec = b"pcm_s16le"
|
853
|
+
elif bits <= 24.0:
|
854
|
+
codec = b"pcm_s24le"
|
855
|
+
|
856
|
+
self.log("conv2 wav", 6)
|
857
|
+
|
858
|
+
# fmt: off
|
859
|
+
cmd = [
|
860
|
+
b"ffmpeg",
|
861
|
+
b"-nostdin",
|
862
|
+
b"-v", b"error",
|
863
|
+
b"-hide_banner",
|
864
|
+
b"-i", fsenc(abspath),
|
865
|
+
b"-map", b"0:a:0",
|
866
|
+
b"-c:a", codec,
|
867
|
+
fsenc(tpath)
|
868
|
+
]
|
869
|
+
# fmt: on
|
870
|
+
self._run_ff(cmd, vn, oom=300)
|
871
|
+
|
806
872
|
def conv_opus(self, abspath , tpath , fmt , vn ) :
|
807
873
|
if self.args.no_acode or not self.args.q_opus:
|
808
874
|
raise Exception("disabled in server config")
|