copyparty 1.13.0__py3-none-any.whl → 1.13.2__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 +55 -5
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +23 -2
- copyparty/broker_mp.py +2 -5
- copyparty/cfg.py +16 -2
- copyparty/fsutil.py +25 -5
- copyparty/httpcli.py +262 -20
- copyparty/httpsrv.py +1 -4
- copyparty/metrics.py +1 -1
- copyparty/mtag.py +72 -3
- copyparty/smbd.py +1 -1
- copyparty/svchub.py +14 -3
- copyparty/tcpsrv.py +6 -0
- copyparty/th_cli.py +5 -0
- copyparty/th_srv.py +32 -5
- copyparty/u2idx.py +18 -3
- copyparty/up2k.py +27 -9
- copyparty/util.py +41 -6
- copyparty/web/a/u2c.py +14 -5
- copyparty/web/baguettebox.js.gz +0 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +1 -1
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/md.html +1 -1
- copyparty/web/mde.html +1 -1
- copyparty/web/msg.html +1 -1
- copyparty/web/splash.html +2 -1
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -1
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/METADATA +55 -8
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/RECORD +37 -37
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/LICENSE +0 -0
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/WHEEL +0 -0
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/entry_points.txt +0 -0
- {copyparty-1.13.0.dist-info → copyparty-1.13.2.dist-info}/top_level.txt +0 -0
copyparty/httpcli.py
CHANGED
@@ -84,6 +84,9 @@ from .util import (
|
|
84
84
|
sanitize_vpath,
|
85
85
|
sendfile_kern,
|
86
86
|
sendfile_py,
|
87
|
+
ub64dec,
|
88
|
+
ub64enc,
|
89
|
+
ujoin,
|
87
90
|
undot,
|
88
91
|
unescape_cookie,
|
89
92
|
unquotep,
|
@@ -213,6 +216,13 @@ class HttpCli(object):
|
|
213
216
|
ka["favico"] = self.args.favico
|
214
217
|
ka["s_name"] = self.args.bname
|
215
218
|
ka["s_doctitle"] = self.args.doctitle
|
219
|
+
ka["tcolor"] = self.vn.flags["tcolor"]
|
220
|
+
|
221
|
+
zso = self.vn.flags.get("html_head")
|
222
|
+
if zso:
|
223
|
+
ka["this"] = self
|
224
|
+
self._build_html_head(zso, ka)
|
225
|
+
|
216
226
|
ka["html_head"] = self.html_head
|
217
227
|
return tpl.render(**ka) # type: ignore
|
218
228
|
|
@@ -360,6 +370,21 @@ class HttpCli(object):
|
|
360
370
|
if "&" in self.req and "?" not in self.req:
|
361
371
|
self.hint = "did you mean '?' instead of '&'"
|
362
372
|
|
373
|
+
if self.args.uqe and "/.uqe/" in self.req:
|
374
|
+
try:
|
375
|
+
vpath, query = self.req.split("?")[0].split("/.uqe/")
|
376
|
+
query = query.split("/")[0] # discard trailing junk
|
377
|
+
# (usually a "filename" to trick discord into behaving)
|
378
|
+
query = ub64dec(query.encode("utf-8")).decode("utf-8", "replace")
|
379
|
+
if query.startswith("/"):
|
380
|
+
self.req = "%s/?%s" % (vpath, query[1:])
|
381
|
+
else:
|
382
|
+
self.req = "%s?%s" % (vpath, query)
|
383
|
+
except Exception as ex:
|
384
|
+
t = "bad uqe in request [%s]: %r" % (self.req, ex)
|
385
|
+
self.loud_reply(t, status=400)
|
386
|
+
return False
|
387
|
+
|
363
388
|
# split req into vpath + uparam
|
364
389
|
uparam = {}
|
365
390
|
if "?" not in self.req:
|
@@ -426,7 +451,8 @@ class HttpCli(object):
|
|
426
451
|
cookie_pw = ""
|
427
452
|
|
428
453
|
if len(uparam) > 10 or len(cookies) > 50:
|
429
|
-
|
454
|
+
self.loud_reply("u wot m8", status=400)
|
455
|
+
return False
|
430
456
|
|
431
457
|
self.uparam = uparam
|
432
458
|
self.cookies = cookies
|
@@ -714,6 +740,31 @@ class HttpCli(object):
|
|
714
740
|
or ("; Trident/" in self.ua and not k304)
|
715
741
|
)
|
716
742
|
|
743
|
+
def _build_html_head(self, maybe_html , kv ) :
|
744
|
+
html = str(maybe_html)
|
745
|
+
is_jinja = html[:2] in "%@%"
|
746
|
+
if is_jinja:
|
747
|
+
html = html.replace("%", "", 1)
|
748
|
+
|
749
|
+
if html.startswith("@"):
|
750
|
+
with open(html[1:], "rb") as f:
|
751
|
+
html = f.read().decode("utf-8")
|
752
|
+
|
753
|
+
if html.startswith("%"):
|
754
|
+
html = html[1:]
|
755
|
+
is_jinja = True
|
756
|
+
|
757
|
+
if is_jinja:
|
758
|
+
print("applying jinja")
|
759
|
+
with self.conn.hsrv.mutex:
|
760
|
+
if html not in self.conn.hsrv.j2:
|
761
|
+
j2env = jinja2.Environment()
|
762
|
+
tpl = j2env.from_string(html)
|
763
|
+
self.conn.hsrv.j2[html] = tpl
|
764
|
+
html = self.conn.hsrv.j2[html].render(**kv)
|
765
|
+
|
766
|
+
self.html_head += html + "\n"
|
767
|
+
|
717
768
|
def send_headers(
|
718
769
|
self,
|
719
770
|
length ,
|
@@ -2247,6 +2298,10 @@ class HttpCli(object):
|
|
2247
2298
|
def handle_login(self) :
|
2248
2299
|
assert self.parser
|
2249
2300
|
pwd = self.parser.require("cppwd", 64)
|
2301
|
+
try:
|
2302
|
+
uhash = self.parser.require("uhash", 256)
|
2303
|
+
except:
|
2304
|
+
uhash = ""
|
2250
2305
|
self.parser.drop()
|
2251
2306
|
|
2252
2307
|
self.out_headerlist = [
|
@@ -2259,6 +2314,11 @@ class HttpCli(object):
|
|
2259
2314
|
|
2260
2315
|
dst += self.ourlq()
|
2261
2316
|
|
2317
|
+
uhash = uhash.lstrip("#")
|
2318
|
+
if uhash not in ("", "-"):
|
2319
|
+
dst += "&" if "?" in dst else "?"
|
2320
|
+
dst += "_=1#" + html_escape(uhash, True, True)
|
2321
|
+
|
2262
2322
|
msg = self.get_pwd_cookie(pwd)
|
2263
2323
|
html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
2264
2324
|
self.reply(html.encode("utf-8"))
|
@@ -3184,7 +3244,7 @@ class HttpCli(object):
|
|
3184
3244
|
data_end = file_size
|
3185
3245
|
break
|
3186
3246
|
|
3187
|
-
if num_need != len(job["need"]):
|
3247
|
+
if num_need != len(job["need"]) and data_end - lower < 8 * M:
|
3188
3248
|
num_need = len(job["need"])
|
3189
3249
|
data_end = 0
|
3190
3250
|
for cid in job["hash"]:
|
@@ -3349,7 +3409,7 @@ class HttpCli(object):
|
|
3349
3409
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
3350
3410
|
cfmt = ""
|
3351
3411
|
if self.thumbcli and not self.args.no_bacode:
|
3352
|
-
for zs in ("opus", "mp3", "w", "j"):
|
3412
|
+
for zs in ("opus", "mp3", "w", "j", "p"):
|
3353
3413
|
if zs in self.ouparam or uarg == zs:
|
3354
3414
|
cfmt = zs
|
3355
3415
|
|
@@ -3480,7 +3540,6 @@ class HttpCli(object):
|
|
3480
3540
|
targs = {
|
3481
3541
|
"r": self.args.SR if self.is_vproxied else "",
|
3482
3542
|
"ts": self.conn.hsrv.cachebuster(),
|
3483
|
-
"html_head": self.html_head,
|
3484
3543
|
"edit": "edit" in self.uparam,
|
3485
3544
|
"title": html_escape(self.vpath, crlf=True),
|
3486
3545
|
"lastmod": int(ts_md * 1000),
|
@@ -3491,6 +3550,13 @@ class HttpCli(object):
|
|
3491
3550
|
"md": boundary,
|
3492
3551
|
"arg_base": arg_base,
|
3493
3552
|
}
|
3553
|
+
|
3554
|
+
zfv = self.vn.flags.get("html_head")
|
3555
|
+
if zfv:
|
3556
|
+
targs["this"] = self
|
3557
|
+
self._build_html_head(zfv, targs)
|
3558
|
+
|
3559
|
+
targs["html_head"] = self.html_head
|
3494
3560
|
zs = template.render(**targs).encode("utf-8", "replace")
|
3495
3561
|
html = zs.split(boundary.encode("utf-8"))
|
3496
3562
|
if len(html) != 2:
|
@@ -3606,8 +3672,6 @@ class HttpCli(object):
|
|
3606
3672
|
self.reply(zb, mime="text/plain; charset=utf-8")
|
3607
3673
|
return True
|
3608
3674
|
|
3609
|
-
self.html_head += self.vn.flags.get("html_head", "")
|
3610
|
-
|
3611
3675
|
html = self.j2s(
|
3612
3676
|
"splash",
|
3613
3677
|
this=self,
|
@@ -3652,7 +3716,7 @@ class HttpCli(object):
|
|
3652
3716
|
return True
|
3653
3717
|
|
3654
3718
|
def set_cfg_reset(self) :
|
3655
|
-
for k in ("k304", "js", "idxh", "cppwd", "cppws"):
|
3719
|
+
for k in ("k304", "js", "idxh", "dots", "cppwd", "cppws"):
|
3656
3720
|
cookie = gencookie(k, "x", self.args.R, False)
|
3657
3721
|
self.out_headerlist.append(("Set-Cookie", cookie))
|
3658
3722
|
|
@@ -3863,7 +3927,7 @@ class HttpCli(object):
|
|
3863
3927
|
allvols = [x for x in allvols if "e2d" in x.flags]
|
3864
3928
|
|
3865
3929
|
for vol in allvols:
|
3866
|
-
cur = idx.get_cur(vol
|
3930
|
+
cur = idx.get_cur(vol)
|
3867
3931
|
if not cur:
|
3868
3932
|
continue
|
3869
3933
|
|
@@ -4078,7 +4142,17 @@ class HttpCli(object):
|
|
4078
4142
|
e2d = "e2d" in vn.flags
|
4079
4143
|
e2t = "e2t" in vn.flags
|
4080
4144
|
|
4081
|
-
|
4145
|
+
add_og = "og" in vn.flags
|
4146
|
+
if add_og:
|
4147
|
+
if "th" in self.uparam or "raw" in self.uparam:
|
4148
|
+
og_ua = add_og = False
|
4149
|
+
elif self.args.og_ua:
|
4150
|
+
og_ua = add_og = self.args.og_ua.search(self.ua)
|
4151
|
+
else:
|
4152
|
+
og_ua = False
|
4153
|
+
add_og = True
|
4154
|
+
og_fn = ""
|
4155
|
+
|
4082
4156
|
if "b" in self.uparam:
|
4083
4157
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
4084
4158
|
|
@@ -4086,13 +4160,15 @@ class HttpCli(object):
|
|
4086
4160
|
is_dk = False
|
4087
4161
|
fk_pass = False
|
4088
4162
|
icur = None
|
4089
|
-
if
|
4163
|
+
if (e2t or e2d) and (is_dir or add_og):
|
4090
4164
|
idx = self.conn.get_u2idx()
|
4091
4165
|
if idx and hasattr(idx, "p_end"):
|
4092
|
-
icur = idx.get_cur(dbv
|
4166
|
+
icur = idx.get_cur(dbv)
|
4093
4167
|
|
4094
4168
|
th_fmt = self.uparam.get("th")
|
4095
|
-
if self.can_read or (
|
4169
|
+
if self.can_read or (
|
4170
|
+
self.can_get and (vn.flags.get("dk") or "fk" not in vn.flags)
|
4171
|
+
):
|
4096
4172
|
if th_fmt is not None:
|
4097
4173
|
nothumb = "dthumb" in dbv.flags
|
4098
4174
|
if is_dir:
|
@@ -4139,7 +4215,7 @@ class HttpCli(object):
|
|
4139
4215
|
elif self.can_write and th_fmt is not None:
|
4140
4216
|
return self.tx_svg("upload\nonly")
|
4141
4217
|
|
4142
|
-
|
4218
|
+
if not self.can_read and self.can_get and self.avn:
|
4143
4219
|
axs = self.avn.axs
|
4144
4220
|
if self.uname not in axs.uhtml:
|
4145
4221
|
pass
|
@@ -4185,6 +4261,17 @@ class HttpCli(object):
|
|
4185
4261
|
self.log(t % (correct, got, self.req, abspath), 6)
|
4186
4262
|
return self.tx_404()
|
4187
4263
|
|
4264
|
+
if add_og:
|
4265
|
+
if og_ua or self.host not in self.headers.get("referer", ""):
|
4266
|
+
self.vpath, og_fn = vsplit(self.vpath)
|
4267
|
+
vpath = self.vpath
|
4268
|
+
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
4269
|
+
abspath = vn.dcanonical(rem)
|
4270
|
+
dbv, vrem = vn.get_dbv(rem)
|
4271
|
+
is_dir = stat.S_ISDIR(st.st_mode)
|
4272
|
+
is_dk = True
|
4273
|
+
vpnodes.pop()
|
4274
|
+
|
4188
4275
|
if (
|
4189
4276
|
(abspath.endswith(".md") or self.can_delete)
|
4190
4277
|
and "nohtml" not in vn.flags
|
@@ -4196,9 +4283,10 @@ class HttpCli(object):
|
|
4196
4283
|
):
|
4197
4284
|
return self.tx_md(vn, abspath)
|
4198
4285
|
|
4199
|
-
|
4200
|
-
|
4201
|
-
|
4286
|
+
if not add_og or not og_fn:
|
4287
|
+
return self.tx_file(
|
4288
|
+
abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
|
4289
|
+
)
|
4202
4290
|
|
4203
4291
|
elif is_dir and not self.can_read:
|
4204
4292
|
if self._use_dirkey(abspath):
|
@@ -4245,7 +4333,11 @@ class HttpCli(object):
|
|
4245
4333
|
is_ls = "ls" in self.uparam
|
4246
4334
|
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
4247
4335
|
|
4248
|
-
if
|
4336
|
+
if (
|
4337
|
+
not is_ls
|
4338
|
+
and not add_og
|
4339
|
+
and (self.ua.startswith("curl/") or self.ua.startswith("fetch"))
|
4340
|
+
):
|
4249
4341
|
self.uparam["ls"] = "v"
|
4250
4342
|
is_ls = True
|
4251
4343
|
|
@@ -4319,6 +4411,7 @@ class HttpCli(object):
|
|
4319
4411
|
"dsort": vf["sort"],
|
4320
4412
|
"dcrop": vf["crop"],
|
4321
4413
|
"dth3x": vf["th3x"],
|
4414
|
+
"dvol": self.args.au_vol,
|
4322
4415
|
"themes": self.args.themes,
|
4323
4416
|
"turbolvl": self.args.turbo,
|
4324
4417
|
"u2j": self.args.u2j,
|
@@ -4370,7 +4463,7 @@ class HttpCli(object):
|
|
4370
4463
|
|
4371
4464
|
for k in ["zip", "tar"]:
|
4372
4465
|
v = self.uparam.get(k)
|
4373
|
-
if v is not None:
|
4466
|
+
if v is not None and (not add_og or not og_fn):
|
4374
4467
|
return self.tx_zip(k, v, self.vpath, vn, rem, [])
|
4375
4468
|
|
4376
4469
|
fsroot, vfs_ls, vfs_virt = vn.ls(
|
@@ -4384,6 +4477,10 @@ class HttpCli(object):
|
|
4384
4477
|
ls_names = [x[0] for x in vfs_ls]
|
4385
4478
|
ls_names.extend(list(vfs_virt.keys()))
|
4386
4479
|
|
4480
|
+
if add_og and og_fn and not self.can_read:
|
4481
|
+
ls_names = [og_fn]
|
4482
|
+
is_js = True
|
4483
|
+
|
4387
4484
|
# check for old versions of files,
|
4388
4485
|
# [num-backups, most-recent, hist-path]
|
4389
4486
|
hist = {}
|
@@ -4445,12 +4542,14 @@ class HttpCli(object):
|
|
4445
4542
|
margin = "DIR"
|
4446
4543
|
elif add_dk:
|
4447
4544
|
zs = absreal(fspath)
|
4448
|
-
margin = '<a href="%s?k=%s&zip" rel="nofollow">zip</a>' % (
|
4545
|
+
margin = '<a href="%s?k=%s&zip=crc" rel="nofollow">zip</a>' % (
|
4449
4546
|
quotep(href),
|
4450
4547
|
self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
|
4451
4548
|
)
|
4452
4549
|
else:
|
4453
|
-
margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (
|
4550
|
+
margin = '<a href="%s?zip=crc" rel="nofollow">zip</a>' % (
|
4551
|
+
quotep(href),
|
4552
|
+
)
|
4454
4553
|
elif fn in hist:
|
4455
4554
|
margin = '<a href="%s.hist/%s">#%s</a>' % (
|
4456
4555
|
base,
|
@@ -4594,6 +4693,9 @@ class HttpCli(object):
|
|
4594
4693
|
else:
|
4595
4694
|
taglist = list(tagset)
|
4596
4695
|
|
4696
|
+
if not files and not dirs and not readme and not logues[0] and not logues[1]:
|
4697
|
+
logues[1] = "this folder is empty"
|
4698
|
+
|
4597
4699
|
if is_ls:
|
4598
4700
|
ls_ret["dirs"] = dirs
|
4599
4701
|
ls_ret["files"] = files
|
@@ -4645,6 +4747,146 @@ class HttpCli(object):
|
|
4645
4747
|
if "mth" in vn.flags:
|
4646
4748
|
j2a["def_hcols"] = list(vn.flags["mth"])
|
4647
4749
|
|
4750
|
+
if add_og and "raw" not in self.uparam:
|
4751
|
+
j2a["this"] = self
|
4752
|
+
cgv["og_fn"] = og_fn
|
4753
|
+
if og_fn and vn.flags.get("og_tpl"):
|
4754
|
+
tpl = vn.flags["og_tpl"]
|
4755
|
+
if "EXT" in tpl:
|
4756
|
+
zs = og_fn.split(".")[-1].lower()
|
4757
|
+
tpl2 = tpl.replace("EXT", zs)
|
4758
|
+
if os.path.exists(tpl2):
|
4759
|
+
tpl = tpl2
|
4760
|
+
with self.conn.hsrv.mutex:
|
4761
|
+
if tpl not in self.conn.hsrv.j2:
|
4762
|
+
tdir, tname = os.path.split(tpl)
|
4763
|
+
j2env = jinja2.Environment()
|
4764
|
+
j2env.loader = jinja2.FileSystemLoader(tdir)
|
4765
|
+
self.conn.hsrv.j2[tpl] = j2env.get_template(tname)
|
4766
|
+
thumb = ""
|
4767
|
+
is_pic = is_vid = is_au = False
|
4768
|
+
covernames = self.args.th_coversd
|
4769
|
+
for fn in ls_names:
|
4770
|
+
if fn.lower() in covernames:
|
4771
|
+
thumb = fn
|
4772
|
+
break
|
4773
|
+
if og_fn:
|
4774
|
+
ext = og_fn.split(".")[-1].lower()
|
4775
|
+
if ext in self.thumbcli.thumbable:
|
4776
|
+
is_pic = (
|
4777
|
+
ext in self.thumbcli.fmt_pil
|
4778
|
+
or ext in self.thumbcli.fmt_vips
|
4779
|
+
or ext in self.thumbcli.fmt_ffi
|
4780
|
+
)
|
4781
|
+
is_vid = ext in self.thumbcli.fmt_ffv
|
4782
|
+
is_au = ext in self.thumbcli.fmt_ffa
|
4783
|
+
if not thumb or not is_au:
|
4784
|
+
thumb = og_fn
|
4785
|
+
file = next((x for x in files if x["name"] == og_fn), None)
|
4786
|
+
else:
|
4787
|
+
file = None
|
4788
|
+
|
4789
|
+
url_base = "%s://%s/%s" % (
|
4790
|
+
"https" if self.is_https else "http",
|
4791
|
+
self.host,
|
4792
|
+
self.args.RS + quotep(vpath),
|
4793
|
+
)
|
4794
|
+
j2a["og_is_pic"] = is_pic
|
4795
|
+
j2a["og_is_vid"] = is_vid
|
4796
|
+
j2a["og_is_au"] = is_au
|
4797
|
+
if thumb:
|
4798
|
+
fmt = vn.flags.get("og_th", "j")
|
4799
|
+
th_base = ujoin(url_base, quotep(thumb))
|
4800
|
+
query = "th=%s&cache" % (fmt,)
|
4801
|
+
query = ub64enc(query.encode("utf-8")).decode("utf-8")
|
4802
|
+
# discord looks at file extension, not content-type...
|
4803
|
+
query += "/th.jpg" if "j" in fmt else "/th.webp"
|
4804
|
+
j2a["og_thumb"] = "%s/.uqe/%s" % (th_base, query)
|
4805
|
+
|
4806
|
+
j2a["og_fn"] = og_fn
|
4807
|
+
j2a["og_file"] = file
|
4808
|
+
if og_fn:
|
4809
|
+
og_fn_q = quotep(og_fn)
|
4810
|
+
query = ub64enc(b"raw").decode("utf-8")
|
4811
|
+
query += "/%s" % (og_fn_q,)
|
4812
|
+
j2a["og_url"] = ujoin(url_base, og_fn_q)
|
4813
|
+
j2a["og_raw"] = j2a["og_url"] + "/.uqe/" + query
|
4814
|
+
else:
|
4815
|
+
j2a["og_url"] = j2a["og_raw"] = url_base
|
4816
|
+
|
4817
|
+
if not vn.flags.get("og_no_head"):
|
4818
|
+
ogh = {"twitter:card": "summary"}
|
4819
|
+
|
4820
|
+
title = str(vn.flags.get("og_title") or "")
|
4821
|
+
|
4822
|
+
if thumb:
|
4823
|
+
ogh["og:image"] = j2a["og_thumb"]
|
4824
|
+
|
4825
|
+
zso = vn.flags.get("og_desc") or ""
|
4826
|
+
if zso != "-":
|
4827
|
+
ogh["og:description"] = str(zso)
|
4828
|
+
|
4829
|
+
zs = vn.flags.get("og_site") or self.args.name
|
4830
|
+
if zs not in ("", "-"):
|
4831
|
+
ogh["og:site_name"] = zs
|
4832
|
+
|
4833
|
+
tagmap = {}
|
4834
|
+
if is_au:
|
4835
|
+
title = str(vn.flags.get("og_title_a") or "")
|
4836
|
+
ogh["og:type"] = "music.song"
|
4837
|
+
ogh["og:audio"] = j2a["og_raw"]
|
4838
|
+
tagmap = {
|
4839
|
+
"artist": "og:music:musician",
|
4840
|
+
"album": "og:music:album",
|
4841
|
+
".dur": "og:music:duration",
|
4842
|
+
}
|
4843
|
+
elif is_vid:
|
4844
|
+
title = str(vn.flags.get("og_title_v") or "")
|
4845
|
+
ogh["og:type"] = "video.other"
|
4846
|
+
ogh["og:video"] = j2a["og_raw"]
|
4847
|
+
tagmap = {
|
4848
|
+
"title": "og:title",
|
4849
|
+
".dur": "og:video:duration",
|
4850
|
+
}
|
4851
|
+
elif is_pic:
|
4852
|
+
title = str(vn.flags.get("og_title_i") or "")
|
4853
|
+
ogh["twitter:card"] = "summary_large_image"
|
4854
|
+
ogh["twitter:image"] = ogh["og:image"] = j2a["og_raw"]
|
4855
|
+
|
4856
|
+
try:
|
4857
|
+
for k, v in file["tags"].items():
|
4858
|
+
zs = "{{ %s }}" % (k,)
|
4859
|
+
title = title.replace(zs, str(v))
|
4860
|
+
except:
|
4861
|
+
pass
|
4862
|
+
title = re.sub(r"\{\{ [^}]+ \}\}", "", title)
|
4863
|
+
while title.startswith(" - "):
|
4864
|
+
title = title[3:]
|
4865
|
+
while title.endswith(" - "):
|
4866
|
+
title = title[:3]
|
4867
|
+
|
4868
|
+
if vn.flags.get("og_s_title") or not title:
|
4869
|
+
title = str(vn.flags.get("og_title") or "")
|
4870
|
+
|
4871
|
+
for tag, hname in tagmap.items():
|
4872
|
+
try:
|
4873
|
+
v = file["tags"][tag]
|
4874
|
+
if not v:
|
4875
|
+
continue
|
4876
|
+
ogh[hname] = int(v) if tag == ".dur" else v
|
4877
|
+
except:
|
4878
|
+
pass
|
4879
|
+
|
4880
|
+
ogh["og:title"] = title
|
4881
|
+
|
4882
|
+
oghs = [
|
4883
|
+
'\t<meta property="%s" content="%s">'
|
4884
|
+
% (k, html_escape(str(v), True, True))
|
4885
|
+
for k, v in ogh.items()
|
4886
|
+
]
|
4887
|
+
zs = self.html_head + "\n%s\n" % ("\n".join(oghs),)
|
4888
|
+
self.html_head = zs.replace("\n\n", "\n")
|
4889
|
+
|
4648
4890
|
html = self.j2s(tpl, **j2a)
|
4649
4891
|
self.reply(html.encode("utf-8", "replace"))
|
4650
4892
|
return True
|
copyparty/httpsrv.py
CHANGED
@@ -262,10 +262,7 @@ class HttpSrv(object):
|
|
262
262
|
msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
263
263
|
self.log(self.name, msg)
|
264
264
|
|
265
|
-
|
266
|
-
self.broker.say("cb_httpsrv_up")
|
267
|
-
|
268
|
-
threading.Thread(target=fun, name="sig-hsrv-up1").start()
|
265
|
+
Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
|
269
266
|
|
270
267
|
while not self.stopping:
|
271
268
|
if self.args.log_conn:
|
copyparty/metrics.py
CHANGED
copyparty/mtag.py
CHANGED
@@ -7,12 +7,15 @@ import os
|
|
7
7
|
import shutil
|
8
8
|
import subprocess as sp
|
9
9
|
import sys
|
10
|
+
import tempfile
|
10
11
|
|
11
12
|
from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
|
13
|
+
from .authsrv import VFS
|
12
14
|
from .bos import bos
|
13
15
|
from .util import (
|
14
16
|
FFMPEG_URL,
|
15
17
|
REKOBO_LKEY,
|
18
|
+
VF_CAREFUL,
|
16
19
|
fsenc,
|
17
20
|
min_ex,
|
18
21
|
pybin,
|
@@ -20,6 +23,7 @@ from .util import (
|
|
20
23
|
runcmd,
|
21
24
|
sfsenc,
|
22
25
|
uncyg,
|
26
|
+
wunlink,
|
23
27
|
)
|
24
28
|
|
25
29
|
def have_ff(scmd ) :
|
@@ -101,6 +105,51 @@ class MParser(object):
|
|
101
105
|
raise Exception()
|
102
106
|
|
103
107
|
|
108
|
+
def au_unpk(log , fmt_map , abspath , vn = None) :
|
109
|
+
ret = ""
|
110
|
+
try:
|
111
|
+
ext = abspath.split(".")[-1].lower()
|
112
|
+
au, pk = fmt_map[ext].split(".")
|
113
|
+
|
114
|
+
fd, ret = tempfile.mkstemp("." + au)
|
115
|
+
|
116
|
+
if pk == "gz":
|
117
|
+
import gzip
|
118
|
+
|
119
|
+
fi = gzip.GzipFile(abspath, mode="rb")
|
120
|
+
|
121
|
+
elif pk == "xz":
|
122
|
+
import lzma
|
123
|
+
|
124
|
+
fi = lzma.open(abspath, "rb")
|
125
|
+
|
126
|
+
elif pk == "zip":
|
127
|
+
import zipfile
|
128
|
+
|
129
|
+
zf = zipfile.ZipFile(abspath, "r")
|
130
|
+
zil = zf.infolist()
|
131
|
+
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
|
132
|
+
fi = zf.open(zil[0])
|
133
|
+
|
134
|
+
with os.fdopen(fd, "wb") as fo:
|
135
|
+
while True:
|
136
|
+
buf = fi.read(32768)
|
137
|
+
if not buf:
|
138
|
+
break
|
139
|
+
|
140
|
+
fo.write(buf)
|
141
|
+
|
142
|
+
return ret
|
143
|
+
|
144
|
+
except Exception as ex:
|
145
|
+
if ret:
|
146
|
+
t = "failed to decompress audio file [%s]: %r"
|
147
|
+
log(t % (abspath, ex))
|
148
|
+
wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
|
149
|
+
|
150
|
+
return abspath
|
151
|
+
|
152
|
+
|
104
153
|
def ffprobe(
|
105
154
|
abspath , timeout = 60
|
106
155
|
) :
|
@@ -275,7 +324,7 @@ class MTag(object):
|
|
275
324
|
or_ffprobe = " or FFprobe"
|
276
325
|
|
277
326
|
if self.backend == "mutagen":
|
278
|
-
self.
|
327
|
+
self._get = self.get_mutagen
|
279
328
|
try:
|
280
329
|
from mutagen import version # noqa: F401
|
281
330
|
except:
|
@@ -284,7 +333,7 @@ class MTag(object):
|
|
284
333
|
|
285
334
|
if self.backend == "ffprobe":
|
286
335
|
self.usable = self.can_ffprobe
|
287
|
-
self.
|
336
|
+
self._get = self.get_ffprobe
|
288
337
|
self.prefer_mt = True
|
289
338
|
|
290
339
|
if not HAVE_FFPROBE:
|
@@ -454,6 +503,17 @@ class MTag(object):
|
|
454
503
|
|
455
504
|
return r1
|
456
505
|
|
506
|
+
def get(self, abspath ) :
|
507
|
+
ext = abspath.split(".")[-1].lower()
|
508
|
+
if ext not in self.args.au_unpk:
|
509
|
+
return self._get(abspath)
|
510
|
+
|
511
|
+
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
512
|
+
ret = self._get(ap)
|
513
|
+
if ap != abspath:
|
514
|
+
wunlink(self.log, ap, VF_CAREFUL)
|
515
|
+
return ret
|
516
|
+
|
457
517
|
def get_mutagen(self, abspath ) :
|
458
518
|
ret = {}
|
459
519
|
|
@@ -547,10 +607,16 @@ class MTag(object):
|
|
547
607
|
except:
|
548
608
|
raise # might be expected outside cpython
|
549
609
|
|
610
|
+
ext = abspath.split(".")[-1].lower()
|
611
|
+
if ext in self.args.au_unpk:
|
612
|
+
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
613
|
+
else:
|
614
|
+
ap = abspath
|
615
|
+
|
550
616
|
ret = {}
|
551
617
|
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
552
618
|
try:
|
553
|
-
cmd = [parser.bin,
|
619
|
+
cmd = [parser.bin, ap]
|
554
620
|
if parser.bin.endswith(".py"):
|
555
621
|
cmd = [pybin] + cmd
|
556
622
|
|
@@ -587,4 +653,7 @@ class MTag(object):
|
|
587
653
|
t = "mtag error: tagname {}, parser {}, file {} => {}"
|
588
654
|
self.log(t.format(tagname, parser.bin, abspath, min_ex()))
|
589
655
|
|
656
|
+
if ap != abspath:
|
657
|
+
wunlink(self.log, ap, VF_CAREFUL)
|
658
|
+
|
590
659
|
return ret
|
copyparty/smbd.py
CHANGED
copyparty/svchub.py
CHANGED
@@ -234,6 +234,10 @@ class SvcHub(object):
|
|
234
234
|
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
235
235
|
decs.pop("ff", None)
|
236
236
|
|
237
|
+
# compressed formats; "s3z=s3m.zip, s3gz=s3m.gz, ..."
|
238
|
+
zlss = [x.strip().lower().split("=", 1) for x in args.au_unpk.split(",")]
|
239
|
+
args.au_unpk = {x[0]: x[1] for x in zlss}
|
240
|
+
|
237
241
|
self.args.th_dec = list(decs.keys())
|
238
242
|
self.thumbsrv = None
|
239
243
|
want_ff = False
|
@@ -274,6 +278,8 @@ class SvcHub(object):
|
|
274
278
|
if not re.match("^(0|[qv][0-9]|[0-9]{2,3}k)$", args.q_mp3.lower()):
|
275
279
|
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
|
276
280
|
raise Exception(t % (args.q_mp3,))
|
281
|
+
else:
|
282
|
+
args.au_unpk = {}
|
277
283
|
|
278
284
|
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
279
285
|
|
@@ -287,13 +293,14 @@ class SvcHub(object):
|
|
287
293
|
from .ftpd import Ftpd
|
288
294
|
|
289
295
|
self.ftpd = None
|
290
|
-
Daemon(self.start_ftpd, "start_ftpd")
|
291
296
|
zms += "f" if args.ftp else "F"
|
292
297
|
|
293
298
|
if args.tftp:
|
294
299
|
from .tftpd import Tftpd
|
295
300
|
|
296
301
|
self.tftpd = None
|
302
|
+
|
303
|
+
if args.ftp or args.ftps or args.tftp:
|
297
304
|
Daemon(self.start_ftpd, "start_tftpd")
|
298
305
|
|
299
306
|
if args.smb:
|
@@ -382,7 +389,7 @@ class SvcHub(object):
|
|
382
389
|
self.sigterm()
|
383
390
|
|
384
391
|
def sigterm(self) :
|
385
|
-
|
392
|
+
self.signal_handler(signal.SIGTERM, None)
|
386
393
|
|
387
394
|
def cb_httpsrv_up(self) :
|
388
395
|
self.httpsrv_up += 1
|
@@ -520,7 +527,7 @@ class SvcHub(object):
|
|
520
527
|
al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
|
521
528
|
al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
|
522
529
|
|
523
|
-
for k in ["no_hash", "no_idx"]:
|
530
|
+
for k in ["no_hash", "no_idx", "og_ua"]:
|
524
531
|
ptn = getattr(self.args, k)
|
525
532
|
if ptn:
|
526
533
|
setattr(self.args, k, re.compile(ptn))
|
@@ -551,6 +558,10 @@ class SvcHub(object):
|
|
551
558
|
except:
|
552
559
|
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
553
560
|
|
561
|
+
al.tcolor = al.tcolor.lstrip("#")
|
562
|
+
if len(al.tcolor) == 3: # fc5 => ffcc55
|
563
|
+
al.tcolor = "".join([x * 2 for x in al.tcolor])
|
564
|
+
|
554
565
|
return True
|
555
566
|
|
556
567
|
def _ipa2re(self, txt) :
|