copyparty 1.16.16__py3-none-any.whl → 1.16.18__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/ftpd.py CHANGED
@@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING
19
19
  from .authsrv import VFS
20
20
  from .bos import bos
21
21
  from .util import (
22
+ FN_EMB,
22
23
  VF_CAREFUL,
23
24
  Daemon,
24
25
  ODict,
@@ -166,6 +167,16 @@ class FtpFs(AbstractedFS):
166
167
  fn = sanitize_fn(fn or "", "")
167
168
  vpath = vjoin(rd, fn)
168
169
  vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
170
+ if (
171
+ w
172
+ and fn.lower() in FN_EMB
173
+ and self.h.uname not in vfs.axs.uread
174
+ and "wo_up_readme" not in vfs.flags
175
+ ):
176
+ fn = "_wo_" + fn
177
+ vpath = vjoin(rd, fn)
178
+ vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
179
+
169
180
  if not vfs.realpath:
170
181
  t = "No filesystem mounted at [{}]"
171
182
  raise FSE(t.format(vpath))
copyparty/httpcli.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import print_function, unicode_literals
4
4
  import argparse # typechk
5
5
  import copy
6
6
  import errno
7
- import gzip
8
7
  import hashlib
9
8
  import itertools
10
9
  import json
@@ -22,6 +21,7 @@ from datetime import datetime
22
21
  from operator import itemgetter
23
22
 
24
23
  import jinja2 # typechk
24
+ from ipaddress import IPv6Network
25
25
 
26
26
  try:
27
27
  if os.environ.get("PRTY_NO_LZMA"):
@@ -45,6 +45,7 @@ from .util import (
45
45
  APPLESAN_RE,
46
46
  BITNESS,
47
47
  DAV_ALLPROPS,
48
+ FN_EMB,
48
49
  HAVE_SQLITE3,
49
50
  HTTPCODE,
50
51
  META_NOBOTS,
@@ -68,6 +69,7 @@ from .util import (
68
69
  get_df,
69
70
  get_spd,
70
71
  guess_mime,
72
+ gzip,
71
73
  gzip_file_orig_sz,
72
74
  gzip_orig_sz,
73
75
  has_resource,
@@ -89,6 +91,7 @@ from .util import (
89
91
  read_socket,
90
92
  read_socket_chunked,
91
93
  read_socket_unbounded,
94
+ read_utf8,
92
95
  relchk,
93
96
  ren_open,
94
97
  runhook,
@@ -382,11 +385,12 @@ class HttpCli(object):
382
385
  t += ' Note: if you are behind cloudflare, then this default header is not a good choice; please first make sure your local reverse-proxy (if any) does not allow non-cloudflare IPs from providing cf-* headers, and then add this additional global setting: "--xff-hdr=cf-connecting-ip"'
383
386
  else:
384
387
  t += ' Note: depending on your reverse-proxy, and/or WAF, and/or other intermediates, you may want to read the true client IP from another header by also specifying "--xff-hdr=SomeOtherHeader"'
385
- zs = (
386
- ".".join(pip.split(".")[:2]) + "."
387
- if "." in pip
388
- else ":".join(pip.split(":")[:4]) + ":"
389
- ) + "0.0/16"
388
+
389
+ if "." in pip:
390
+ zs = ".".join(pip.split(".")[:2]) + ".0.0/16"
391
+ else:
392
+ zs = IPv6Network(pip + "/64", False).compressed
393
+
390
394
  zs2 = ' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
391
395
  self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs, zs2), 3)
392
396
  self.bad_xff = True
@@ -863,8 +867,7 @@ class HttpCli(object):
863
867
  html = html.replace("%", "", 1)
864
868
 
865
869
  if html.startswith("@"):
866
- with open(html[1:], "rb") as f:
867
- html = f.read().decode("utf-8")
870
+ html = read_utf8(self.log, html[1:], True)
868
871
 
869
872
  if html.startswith("%"):
870
873
  html = html[1:]
@@ -1231,14 +1234,7 @@ class HttpCli(object):
1231
1234
  return self.tx_404(True)
1232
1235
  else:
1233
1236
  vfs = self.asrv.vfs
1234
- if (
1235
- not vfs.nodes
1236
- and not vfs.axs.uread
1237
- and not vfs.axs.uwrite
1238
- and not vfs.axs.uget
1239
- and not vfs.axs.uhtml
1240
- and not vfs.axs.uadmin
1241
- ):
1237
+ if vfs.badcfg1:
1242
1238
  t = "<h2>access denied due to failsafe; check server log</h2>"
1243
1239
  html = self.j2s("splash", this=self, msg=t)
1244
1240
  self.reply(html.encode("utf-8", "replace"), 500)
@@ -2544,6 +2540,16 @@ class HttpCli(object):
2544
2540
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2545
2541
  dbv, vrem = vfs.get_dbv(rem)
2546
2542
 
2543
+ name = sanitize_fn(name, "")
2544
+ if (
2545
+ not self.can_read
2546
+ and self.can_write
2547
+ and name.lower() in FN_EMB
2548
+ and "wo_up_readme" not in dbv.flags
2549
+ ):
2550
+ name = "_wo_" + name
2551
+
2552
+ body["name"] = name
2547
2553
  body["vtop"] = dbv.vpath
2548
2554
  body["ptop"] = dbv.realpath
2549
2555
  body["prel"] = vrem
@@ -3720,8 +3726,7 @@ class HttpCli(object):
3720
3726
  continue
3721
3727
  fn = "%s/%s" % (abspath, fn)
3722
3728
  if bos.path.isfile(fn):
3723
- with open(fsenc(fn), "rb") as f:
3724
- logues[n] = f.read().decode("utf-8")
3729
+ logues[n] = read_utf8(self.log, fsenc(fn), False)
3725
3730
  if "exp" in vn.flags:
3726
3731
  logues[n] = self._expand(
3727
3732
  logues[n], vn.flags.get("exp_lg") or []
@@ -3742,9 +3747,8 @@ class HttpCli(object):
3742
3747
  for fn in fns:
3743
3748
  fn = "%s/%s" % (abspath, fn)
3744
3749
  if bos.path.isfile(fn):
3745
- with open(fsenc(fn), "rb") as f:
3746
- txt = f.read().decode("utf-8")
3747
- break
3750
+ txt = read_utf8(self.log, fsenc(fn), False)
3751
+ break
3748
3752
 
3749
3753
  if txt and "exp" in vn.flags:
3750
3754
  txt = self._expand(txt, vn.flags.get("exp_md") or [])
@@ -3777,6 +3781,19 @@ class HttpCli(object):
3777
3781
 
3778
3782
  return txt
3779
3783
 
3784
+ def _can_zip(self, volflags ) :
3785
+ lvl = volflags["zip_who"]
3786
+ if self.args.no_zip or not lvl:
3787
+ return "download-as-zip/tar is disabled in server config"
3788
+ elif lvl <= 1 and not self.can_admin:
3789
+ return "download-as-zip/tar is admin-only on this server"
3790
+ elif lvl <= 2 and self.uname in ("", "*"):
3791
+ return "you must be authenticated to download-as-zip/tar on this server"
3792
+ elif self.args.ua_nozip and self.args.ua_nozip.search(self.ua):
3793
+ t = "this URL contains no valuable information for bots/crawlers"
3794
+ raise Pebkac(403, t)
3795
+ return ""
3796
+
3780
3797
  def tx_res(self, req_path ) :
3781
3798
  status = 200
3782
3799
  logmsg = "{:4} {} ".format("", self.req)
@@ -4307,13 +4324,8 @@ class HttpCli(object):
4307
4324
  rem ,
4308
4325
  items ,
4309
4326
  ) :
4310
- lvl = vn.flags["zip_who"]
4311
- if self.args.no_zip or not lvl:
4312
- raise Pebkac(400, "download-as-zip/tar is disabled in server config")
4313
- elif lvl <= 1 and not self.can_admin:
4314
- raise Pebkac(400, "download-as-zip/tar is admin-only on this server")
4315
- elif lvl <= 2 and self.uname in ("", "*"):
4316
- t = "you must be authenticated to download-as-zip/tar on this server"
4327
+ t = self._can_zip(vn.flags)
4328
+ if t:
4317
4329
  raise Pebkac(400, t)
4318
4330
 
4319
4331
  logmsg = "{:4} {} ".format("", self.req)
@@ -4346,6 +4358,33 @@ class HttpCli(object):
4346
4358
  else:
4347
4359
  fn = self.host.split(":")[0]
4348
4360
 
4361
+ if vn.flags.get("zipmax") and (not self.uname or not "zipmaxu" in vn.flags):
4362
+ maxs = vn.flags.get("zipmaxs_v") or 0
4363
+ maxn = vn.flags.get("zipmaxn_v") or 0
4364
+ nf = 0
4365
+ nb = 0
4366
+ fgen = vn.zipgen(
4367
+ vpath, rem, set(items), self.uname, False, not self.args.no_scandir
4368
+ )
4369
+ t = "total size exceeds a limit specified in server config"
4370
+ t = vn.flags.get("zipmaxt") or t
4371
+ if maxs and maxn:
4372
+ for zd in fgen:
4373
+ nf += 1
4374
+ nb += zd["st"].st_size
4375
+ if maxs < nb or maxn < nf:
4376
+ raise Pebkac(400, t)
4377
+ elif maxs:
4378
+ for zd in fgen:
4379
+ nb += zd["st"].st_size
4380
+ if maxs < nb:
4381
+ raise Pebkac(400, t)
4382
+ elif maxn:
4383
+ for zd in fgen:
4384
+ nf += 1
4385
+ if maxn < nf:
4386
+ raise Pebkac(400, t)
4387
+
4349
4388
  safe = (string.ascii_letters + string.digits).replace("%", "")
4350
4389
  afn = "".join([x if x in safe.replace('"', "") else "_" for x in fn])
4351
4390
  bascii = unicode(safe).encode("utf-8")
@@ -4991,6 +5030,8 @@ class HttpCli(object):
4991
5030
  def get_dls(self) :
4992
5031
  ret = []
4993
5032
  dls = self.conn.hsrv.tdls
5033
+ enshare = self.args.shr
5034
+ shrs = enshare[1:]
4994
5035
  for dl_id, (t0, sz, vn, vp, uname) in self.conn.hsrv.tdli.items():
4995
5036
  t1, sent = dls[dl_id]
4996
5037
  if sent > 0x100000: # 1m; buffers 2~4
@@ -4999,6 +5040,15 @@ class HttpCli(object):
4999
5040
  vp = ""
5000
5041
  elif self.uname not in vn.axs.udot and (vp.startswith(".") or "/." in vp):
5001
5042
  vp = ""
5043
+ elif (
5044
+ enshare
5045
+ and vp.startswith(shrs)
5046
+ and self.uname != vn.shr_owner
5047
+ and self.uname not in vn.axs.uadmin
5048
+ and self.uname not in self.args.shr_adm
5049
+ and not dl_id.startswith(self.ip + ":")
5050
+ ):
5051
+ vp = ""
5002
5052
  if self.uname not in vn.axs.uadmin:
5003
5053
  dl_id = uname = ""
5004
5054
 
@@ -5980,6 +6030,8 @@ class HttpCli(object):
5980
6030
  zs = self.gen_fk(2, self.args.dk_salt, abspath, 0, 0)[:add_dk]
5981
6031
  ls_ret["dk"] = cgv["dk"] = zs
5982
6032
 
6033
+ no_zip = bool(self._can_zip(vf))
6034
+
5983
6035
  dirs = []
5984
6036
  files = []
5985
6037
  ptn_hr = RE_HR
@@ -6005,7 +6057,7 @@ class HttpCli(object):
6005
6057
  is_dir = stat.S_ISDIR(inf.st_mode)
6006
6058
  if is_dir:
6007
6059
  href += "/"
6008
- if self.args.no_zip:
6060
+ if no_zip:
6009
6061
  margin = "DIR"
6010
6062
  elif add_dk:
6011
6063
  zs = absreal(fspath)
@@ -6018,7 +6070,7 @@ class HttpCli(object):
6018
6070
  quotep(href),
6019
6071
  )
6020
6072
  elif fn in hist:
6021
- margin = '<a href="%s.hist/%s">#%s</a>' % (
6073
+ margin = '<a href="%s.hist/%s" rel="nofollow">#%s</a>' % (
6022
6074
  base,
6023
6075
  html_escape(hist[fn][2], quot=True, crlf=True),
6024
6076
  hist[fn][0],
@@ -6219,6 +6271,10 @@ class HttpCli(object):
6219
6271
 
6220
6272
  doc = self.uparam.get("doc") if self.can_read else None
6221
6273
  if doc:
6274
+ zp = self.args.ua_nodoc
6275
+ if zp and zp.search(self.ua):
6276
+ t = "this URL contains no valuable information for bots/crawlers"
6277
+ raise Pebkac(403, t)
6222
6278
  j2a["docname"] = doc
6223
6279
  doctxt = None
6224
6280
  dfn = lnames.get(doc.lower())
@@ -6229,9 +6285,7 @@ class HttpCli(object):
6229
6285
  docpath = os.path.join(abspath, doc)
6230
6286
  sz = bos.path.getsize(docpath)
6231
6287
  if sz < 1024 * self.args.txt_max:
6232
- with open(fsenc(docpath), "rb") as f:
6233
- doctxt = f.read().decode("utf-8", "replace")
6234
-
6288
+ doctxt = read_utf8(self.log, fsenc(docpath), False)
6235
6289
  if doc.lower().endswith(".md") and "exp" in vn.flags:
6236
6290
  doctxt = self._expand(doctxt, vn.flags.get("exp_md") or [])
6237
6291
  else:
copyparty/mtag.py CHANGED
@@ -18,6 +18,7 @@ from .util import (
18
18
  REKOBO_LKEY,
19
19
  VF_CAREFUL,
20
20
  fsenc,
21
+ gzip,
21
22
  min_ex,
22
23
  pybin,
23
24
  retchk,
@@ -132,8 +133,6 @@ def au_unpk(
132
133
  fd, ret = tempfile.mkstemp("." + au)
133
134
 
134
135
  if pk == "gz":
135
- import gzip
136
-
137
136
  fi = gzip.GzipFile(abspath, mode="rb")
138
137
 
139
138
  elif pk == "xz":
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,6 +56,7 @@ from .util import (
57
56
  ansi_re,
58
57
  build_netmap,
59
58
  expat_ver,
59
+ gzip,
60
60
  load_ipu,
61
61
  min_ex,
62
62
  mp,
@@ -759,7 +759,8 @@ class SvcHub(object):
759
759
  vs = os.path.expandvars(os.path.expanduser(vs))
760
760
  setattr(al, k, vs)
761
761
 
762
- for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
762
+ zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
763
+ for k in zs.split(" "):
763
764
  vs = getattr(al, k)
764
765
  if not vs or vs == "no":
765
766
  setattr(al, k, None)
@@ -1250,7 +1251,7 @@ class SvcHub(object):
1250
1251
  raise
1251
1252
 
1252
1253
  def check_mp_support(self) :
1253
- if MACOS:
1254
+ if MACOS and not os.environ.get("PRTY_FORCE_MP"):
1254
1255
  return "multiprocessing is wonky on mac osx;"
1255
1256
  elif sys.version_info < (3, 3):
1256
1257
  return "need python 3.3 or newer for multiprocessing;"
@@ -1270,7 +1271,7 @@ class SvcHub(object):
1270
1271
  return False
1271
1272
 
1272
1273
  try:
1273
- if mp.cpu_count() <= 1:
1274
+ if mp.cpu_count() <= 1 and not os.environ.get("PRTY_FORCE_MP"):
1274
1275
  raise Exception()
1275
1276
  except:
1276
1277
  self.log("svchub", "only one CPU detected; multiprocessing disabled")
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 UTC, BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
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(self, caller , vpath , perms , *a ) :
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
- return vfs, vfs.canonical(rem)
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])[1]
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])[1])
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/up2k.py CHANGED
@@ -2,7 +2,6 @@
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
4
  import errno
5
- import gzip
6
5
  import hashlib
7
6
  import json
8
7
  import math
@@ -42,6 +41,7 @@ from .util import (
42
41
  fsenc,
43
42
  gen_filekey,
44
43
  gen_filekey_dbg,
44
+ gzip,
45
45
  hidedir,
46
46
  humansize,
47
47
  min_ex,
@@ -1112,7 +1112,7 @@ class Up2k(object):
1112
1112
  ft = "\033[0;32m{}{:.0}"
1113
1113
  ff = "\033[0;35m{}{:.0}"
1114
1114
  fv = "\033[0;36m{}:\033[90m{}"
1115
- zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
1115
+ zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1116
1116
  fx = set(zs.split())
1117
1117
  fd = vf_bmap()
1118
1118
  fd.update(vf_cmap())
@@ -2903,7 +2903,6 @@ class Up2k(object):
2903
2903
  if ptop not in self.registry:
2904
2904
  raise Pebkac(410, "location unavailable")
2905
2905
 
2906
- cj["name"] = sanitize_fn(cj["name"], "")
2907
2906
  cj["poke"] = now = self.db_act = self.vol_act[ptop] = time.time()
2908
2907
  wark = dwark = self._get_wark(cj)
2909
2908
  job = None
@@ -3220,6 +3219,7 @@ class Up2k(object):
3220
3219
  job["ptop"] = vfs.realpath
3221
3220
  job["vtop"] = vfs.vpath
3222
3221
  job["prel"] = rem
3222
+ job["name"] = sanitize_fn(job["name"], "")
3223
3223
  if zvfs.vpath != vfs.vpath:
3224
3224
  # print(json.dumps(job, sort_keys=True, indent=4))
3225
3225
  job["hash"] = cj["hash"]
@@ -3410,6 +3410,7 @@ class Up2k(object):
3410
3410
  rm = False,
3411
3411
  lmod = 0,
3412
3412
  fsrc = None,
3413
+ is_mv = False,
3413
3414
  ) :
3414
3415
  if src == dst or (fsrc and fsrc == dst):
3415
3416
  t = "symlinking a file to itself?? orig(%s) fsrc(%s) link(%s)"
@@ -3426,7 +3427,7 @@ class Up2k(object):
3426
3427
 
3427
3428
  linked = False
3428
3429
  try:
3429
- if not flags.get("dedup"):
3430
+ if not is_mv and not flags.get("dedup"):
3430
3431
  raise Exception("dedup is disabled in config")
3431
3432
 
3432
3433
  lsrc = src
@@ -3691,8 +3692,9 @@ class Up2k(object):
3691
3692
  if self.idx_wark(vflags, *z2):
3692
3693
  del self.registry[ptop][wark]
3693
3694
  else:
3694
- for k in "host tnam busy sprs poke t0c".split():
3695
+ for k in "host tnam busy sprs poke".split():
3695
3696
  del job[k]
3697
+ job.pop("t0c", None)
3696
3698
  job["t0"] = int(job["t0"])
3697
3699
  job["hash"] = []
3698
3700
  job["done"] = 1
@@ -4578,7 +4580,7 @@ class Up2k(object):
4578
4580
  dlink = bos.readlink(sabs)
4579
4581
  dlink = os.path.join(os.path.dirname(sabs), dlink)
4580
4582
  dlink = bos.path.abspath(dlink)
4581
- self._symlink(dlink, dabs, dvn.flags, lmod=ftime)
4583
+ self._symlink(dlink, dabs, dvn.flags, lmod=ftime, is_mv=True)
4582
4584
  wunlink(self.log, sabs, svn.flags)
4583
4585
  else:
4584
4586
  atomic_move(self.log, sabs, dabs, svn.flags)
@@ -4796,7 +4798,7 @@ class Up2k(object):
4796
4798
  flags = self.flags.get(ptop) or {}
4797
4799
  atomic_move(self.log, sabs, slabs, flags)
4798
4800
  bos.utime(slabs, (int(time.time()), int(mt)), False)
4799
- self._symlink(slabs, sabs, flags, False)
4801
+ self._symlink(slabs, sabs, flags, False, is_mv=True)
4800
4802
  full[slabs] = (ptop, rem)
4801
4803
  sabs = slabs
4802
4804
 
@@ -4855,7 +4857,9 @@ class Up2k(object):
4855
4857
  # (for example a volume with symlinked dupes but no --dedup);
4856
4858
  # fsrc=sabs is then a source that currently resolves to copy
4857
4859
 
4858
- self._symlink(dabs, alink, flags, False, lmod=lmod or 0, fsrc=sabs)
4860
+ self._symlink(
4861
+ dabs, alink, flags, False, lmod=lmod or 0, fsrc=sabs, is_mv=True
4862
+ )
4859
4863
 
4860
4864
  return len(full) + len(links)
4861
4865
 
@@ -4969,6 +4973,7 @@ class Up2k(object):
4969
4973
  job["ptop"] = vfs.realpath
4970
4974
  job["vtop"] = vfs.vpath
4971
4975
  job["prel"] = rem
4976
+ job["name"] = sanitize_fn(job["name"], "")
4972
4977
  if zvfs.vpath != vfs.vpath:
4973
4978
  self.log("xbu reloc2:%d..." % (depth,), 6)
4974
4979
  return self._handle_json(job, depth + 1)