copyparty 1.15.5__py3-none-any.whl → 1.15.7__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 CHANGED
@@ -772,7 +772,7 @@ def get_sects():
772
772
  dedent(
773
773
  """
774
774
  specify --exp or the "exp" volflag to enable placeholder expansions
775
- in README.md / .prologue.html / .epilogue.html
775
+ in README.md / PREADME.md / .prologue.html / .epilogue.html
776
776
 
777
777
  --exp-md (volflag exp_md) holds the list of placeholders which can be
778
778
  expanded in READMEs, and --exp-lg (volflag exp_lg) likewise for logues;
@@ -888,7 +888,7 @@ def get_sects():
888
888
  dedent(
889
889
  """
890
890
  the mDNS protocol is multicast-based, which means there are thousands
891
- of fun and intersesting ways for it to break unexpectedly
891
+ of fun and interesting ways for it to break unexpectedly
892
892
 
893
893
  things to check if it does not work at all:
894
894
 
@@ -997,6 +997,7 @@ def add_upload(ap):
997
997
  ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
998
998
  ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
999
999
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
1000
+ ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)")
1000
1001
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
1001
1002
  ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
1002
1003
  ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
@@ -1078,6 +1079,7 @@ def add_auth(ap):
1078
1079
  ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
1079
1080
  ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)")
1080
1081
  ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
1082
+ ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]")
1081
1083
 
1082
1084
 
1083
1085
  def add_chpw(ap):
@@ -1246,7 +1248,7 @@ def add_safety(ap):
1246
1248
  ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
1247
1249
  ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
1248
1250
  ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
1249
- ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
1251
+ ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings")
1250
1252
  ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
1251
1253
  ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
1252
1254
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
@@ -1444,7 +1446,7 @@ def add_ui(ap, retry):
1444
1446
  ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
1445
1447
  ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox")
1446
1448
  ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for prologue/epilogue docs (volflag=lg_sbf)")
1447
- ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README.md documents (volflags: no_sb_md | sb_md)")
1449
+ ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1448
1450
  ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
1449
1451
 
1450
1452
 
@@ -1468,6 +1470,7 @@ def add_debug(ap):
1468
1470
  ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to \033[33m--bf-nc\033[0m and \033[33m--bf-dir\033[0m")
1469
1471
  ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than \033[33mNUM\033[0m files at \033[33m--kf-dir\033[0m already; default: 6.3 GiB max (200*32M)")
1470
1472
  ap2.add_argument("--bf-dir", metavar="PATH", type=u, default="bf", help="bak-flips: store corrupted chunks at \033[33mPATH\033[0m; default: folder named 'bf' wherever copyparty was started")
1473
+ ap2.add_argument("--bf-log", metavar="PATH", type=u, default="", help="bak-flips: log corruption info to a textfile at \033[33mPATH\033[0m")
1471
1474
 
1472
1475
 
1473
1476
  # fmt: on
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 15, 5)
3
+ VERSION = (1, 15, 7)
4
4
  CODENAME = "fill the drives"
5
- BUILD_DT = (2024, 10, 5)
5
+ BUILD_DT = (2024, 10, 14)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -917,7 +917,7 @@ class AuthSrv(object):
917
917
 
918
918
  for un, gn in un_gn:
919
919
  # if ap/vp has a user/group placeholder, make sure to keep
920
- # track so the same user/gruop is mapped when setting perms;
920
+ # track so the same user/group is mapped when setting perms;
921
921
  # otherwise clear un/gn to indicate it's a regular volume
922
922
 
923
923
  src1 = src0.replace("${u}", un or "\n")
copyparty/cfg.py CHANGED
@@ -13,6 +13,7 @@ def vf_bmap() :
13
13
  "dav_rt": "davrt",
14
14
  "ed": "dots",
15
15
  "hardlink_only": "hardlinkonly",
16
+ "no_clone": "noclone",
16
17
  "no_dirsz": "nodirsz",
17
18
  "no_dupe": "nodupe",
18
19
  "no_forget": "noforget",
@@ -135,7 +136,8 @@ flagcats = {
135
136
  "hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
136
137
  "hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
137
138
  "safededup": "verify on-disk data before using it for dedup",
138
- "nodupe": "rejects existing files (instead of symlinking them)",
139
+ "noclone": "take dupe data from clients, even if available on HDD",
140
+ "nodupe": "rejects existing files (instead of linking/cloning them)",
139
141
  "sparse": "force use of sparse files, mainly for s3-backed storage",
140
142
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
141
143
  "nosub": "forces all uploads into the top folder of the vfs",
copyparty/ftpd.py CHANGED
@@ -72,6 +72,7 @@ class FtpAuth(DummyAuthorizer):
72
72
  else:
73
73
  raise AuthenticationFailed("banned")
74
74
 
75
+ args = self.hub.args
75
76
  asrv = self.hub.asrv
76
77
  uname = "*"
77
78
  if username != "anonymous":
@@ -82,6 +83,9 @@ class FtpAuth(DummyAuthorizer):
82
83
  uname = zs
83
84
  break
84
85
 
86
+ if args.ipu and uname == "*":
87
+ uname = args.ipu_iu[args.ipu_nm.map(ip)]
88
+
85
89
  if not uname or not (asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname)):
86
90
  g = self.hub.gpwd
87
91
  if g.lim:
copyparty/httpcli.py CHANGED
@@ -123,6 +123,10 @@ _ = (argparse, threading)
123
123
 
124
124
  NO_CACHE = {"Cache-Control": "no-cache"}
125
125
 
126
+ LOGUES = [[0, ".prologue.html"], [1, ".epilogue.html"]]
127
+
128
+ READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
129
+
126
130
 
127
131
  class HttpCli(object):
128
132
  """
@@ -580,6 +584,9 @@ class HttpCli(object):
580
584
  or "*"
581
585
  )
582
586
 
587
+ if self.args.ipu and self.uname == "*":
588
+ self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
589
+
583
590
  self.rvol = self.asrv.vfs.aread[self.uname]
584
591
  self.wvol = self.asrv.vfs.awrite[self.uname]
585
592
  self.avol = self.asrv.vfs.aadmin[self.uname]
@@ -2019,13 +2026,32 @@ class HttpCli(object):
2019
2026
  return True
2020
2027
 
2021
2028
  def bakflip(
2022
- self, f , ofs , sz , sha , flags
2029
+ self,
2030
+ f ,
2031
+ ap ,
2032
+ ofs ,
2033
+ sz ,
2034
+ good_sha ,
2035
+ bad_sha ,
2036
+ flags ,
2023
2037
  ) :
2038
+ now = time.time()
2039
+ t = "bad-chunk: %.3f %s %s %d %s %s %s"
2040
+ t = t % (now, bad_sha, good_sha, ofs, self.ip, self.uname, ap)
2041
+ self.log(t, 5)
2042
+
2043
+ if self.args.bf_log:
2044
+ try:
2045
+ with open(self.args.bf_log, "ab+") as f2:
2046
+ f2.write((t + "\n").encode("utf-8", "replace"))
2047
+ except Exception as ex:
2048
+ self.log("append %s failed: %r" % (self.args.bf_log, ex))
2049
+
2024
2050
  if not self.args.bak_flips or self.args.nw:
2025
2051
  return
2026
2052
 
2027
2053
  sdir = self.args.bf_dir
2028
- fp = os.path.join(sdir, sha)
2054
+ fp = os.path.join(sdir, bad_sha)
2029
2055
  if bos.path.exists(fp):
2030
2056
  return self.log("no bakflip; have it", 6)
2031
2057
 
@@ -2145,11 +2171,17 @@ class HttpCli(object):
2145
2171
  except UnrecvEOF:
2146
2172
  raise Pebkac(422, "client disconnected while posting JSON")
2147
2173
 
2148
- self.log("decoding {} bytes of {} json".format(len(json_buf), enc))
2149
2174
  try:
2150
2175
  body = json.loads(json_buf.decode(enc, "replace"))
2176
+ try:
2177
+ zds = {k: v for k, v in body.items()}
2178
+ zds["hash"] = "%d chunks" % (len(body["hash"]))
2179
+ except:
2180
+ zds = body
2181
+ t = "POST len=%d type=%s ip=%s user=%s req=%r json=%s"
2182
+ self.log(t % (len(json_buf), enc, self.ip, self.uname, self.req, zds))
2151
2183
  except:
2152
- raise Pebkac(422, "you POSTed invalid json")
2184
+ raise Pebkac(422, "you POSTed %d bytes of invalid json" % (len(json_buf),))
2153
2185
 
2154
2186
  # self.reply(b"cloudflare", 503)
2155
2187
  # return True
@@ -2347,7 +2379,9 @@ class HttpCli(object):
2347
2379
 
2348
2380
  if sha_b64 != chash:
2349
2381
  try:
2350
- self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
2382
+ self.bakflip(
2383
+ f, path, cstart[0], post_sz, chash, sha_b64, vfs.flags
2384
+ )
2351
2385
  except:
2352
2386
  self.log("bakflip failed: " + min_ex())
2353
2387
 
@@ -2404,13 +2438,13 @@ class HttpCli(object):
2404
2438
  finally:
2405
2439
  if locked:
2406
2440
  # now block until all chunks released+confirmed
2407
- x = broker.ask("up2k.confirm_chunks", ptop, wark, locked)
2441
+ x = broker.ask("up2k.confirm_chunks", ptop, wark, written, locked)
2408
2442
  num_left, t = x.get()
2409
2443
  if num_left < 0:
2410
2444
  self.loud_reply(t, status=500)
2411
2445
  return False
2412
2446
  t = "got %d more chunks, %d left"
2413
- self.log(t % (len(locked), num_left), 6)
2447
+ self.log(t % (len(written), num_left), 6)
2414
2448
 
2415
2449
  if num_left < 0:
2416
2450
  raise Pebkac(500, "unconfirmed; see serverlog")
@@ -3022,7 +3056,7 @@ class HttpCli(object):
3022
3056
  if ex.errno != errno.ENOENT:
3023
3057
  raise
3024
3058
 
3025
- # if file exists, chekc that timestamp matches the client's
3059
+ # if file exists, check that timestamp matches the client's
3026
3060
  if srv_lastmod >= 0:
3027
3061
  same_lastmod = cli_lastmod3 in [-1, srv_lastmod3]
3028
3062
  if not same_lastmod:
@@ -3233,7 +3267,7 @@ class HttpCli(object):
3233
3267
  ) :
3234
3268
  logues = ["", ""]
3235
3269
  if not self.args.no_logues:
3236
- for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
3270
+ for n, fn in LOGUES:
3237
3271
  if lnames is not None and fn not in lnames:
3238
3272
  continue
3239
3273
  fn = "%s/%s" % (abspath, fn)
@@ -3245,25 +3279,31 @@ class HttpCli(object):
3245
3279
  logues[n], vn.flags.get("exp_lg") or []
3246
3280
  )
3247
3281
 
3248
- readme = ""
3249
- if not self.args.no_readme and not logues[1]:
3250
- if lnames is None:
3251
- fns = ["README.md", "readme.md"]
3252
- elif "readme.md" in lnames:
3253
- fns = [lnames["readme.md"]]
3282
+ readmes = ["", ""]
3283
+ for n, fns in [] if self.args.no_readme else READMES:
3284
+ if logues[n]:
3285
+ continue
3286
+ elif lnames is None:
3287
+ pass
3288
+ elif fns[0] in lnames:
3289
+ fns = [lnames[fns[0]]]
3254
3290
  else:
3255
3291
  fns = []
3256
3292
 
3293
+ txt = ""
3257
3294
  for fn in fns:
3258
3295
  fn = "%s/%s" % (abspath, fn)
3259
3296
  if bos.path.isfile(fn):
3260
3297
  with open(fsenc(fn), "rb") as f:
3261
- readme = f.read().decode("utf-8")
3298
+ txt = f.read().decode("utf-8")
3262
3299
  break
3263
- if readme and "exp" in vn.flags:
3264
- readme = self._expand(readme, vn.flags.get("exp_md") or [])
3265
3300
 
3266
- return logues, readme
3301
+ if txt and "exp" in vn.flags:
3302
+ txt = self._expand(txt, vn.flags.get("exp_md") or [])
3303
+
3304
+ readmes[n] = txt
3305
+
3306
+ return logues, readmes
3267
3307
 
3268
3308
  def _expand(self, txt , phs ) :
3269
3309
  for ph in phs:
@@ -4763,7 +4803,7 @@ class HttpCli(object):
4763
4803
 
4764
4804
  fmt = fmt.format(len(nfmt.format(biggest)))
4765
4805
  retl = [
4766
- "# {}: {}".format(x, ls[x])
4806
+ ("# %s: %s" % (x, ls[x])).replace(r"</span> // <span>", " // ")
4767
4807
  for x in ["acct", "perms", "srvinf"]
4768
4808
  if x in ls
4769
4809
  ]
@@ -5116,9 +5156,9 @@ class HttpCli(object):
5116
5156
  j2a["no_prism"] = True
5117
5157
 
5118
5158
  if not self.can_read and not is_dk:
5119
- logues, readme = self._add_logues(vn, abspath, None)
5159
+ logues, readmes = self._add_logues(vn, abspath, None)
5120
5160
  ls_ret["logues"] = j2a["logues"] = logues
5121
- ls_ret["readme"] = cgv["readme"] = readme
5161
+ ls_ret["readmes"] = cgv["readmes"] = readmes
5122
5162
 
5123
5163
  if is_ls:
5124
5164
  return self.tx_ls(ls_ret)
@@ -5374,11 +5414,18 @@ class HttpCli(object):
5374
5414
  else:
5375
5415
  taglist = list(tagset)
5376
5416
 
5377
- logues, readme = self._add_logues(vn, abspath, lnames)
5417
+ logues, readmes = self._add_logues(vn, abspath, lnames)
5378
5418
  ls_ret["logues"] = j2a["logues"] = logues
5379
- ls_ret["readme"] = cgv["readme"] = readme
5419
+ ls_ret["readmes"] = cgv["readmes"] = readmes
5380
5420
 
5381
- if not files and not dirs and not readme and not logues[0] and not logues[1]:
5421
+ if (
5422
+ not files
5423
+ and not dirs
5424
+ and not readmes[0]
5425
+ and not readmes[1]
5426
+ and not logues[0]
5427
+ and not logues[1]
5428
+ ):
5382
5429
  logues[1] = "this folder is empty"
5383
5430
 
5384
5431
  if "descript.ion" in lnames and os.path.isfile(
@@ -5423,7 +5470,11 @@ class HttpCli(object):
5423
5470
  if doc:
5424
5471
  j2a["docname"] = doc
5425
5472
  doctxt = None
5426
- if next((x for x in files if x["name"] == doc), None):
5473
+ dfn = lnames.get(doc.lower())
5474
+ if dfn and dfn != doc:
5475
+ # found Foo but want FOO
5476
+ dfn = next((x for x in files if x["name"] == doc), None)
5477
+ if dfn:
5427
5478
  docpath = os.path.join(abspath, doc)
5428
5479
  sz = bos.path.getsize(docpath)
5429
5480
  if sz < 1024 * self.args.txt_max:
copyparty/httpconn.py CHANGED
@@ -56,6 +56,8 @@ class HttpConn(object):
56
56
  self.asrv = hsrv.asrv # mypy404
57
57
  self.u2fh = hsrv.u2fh # mypy404
58
58
  self.pipes = hsrv.pipes # mypy404
59
+ self.ipu_iu = hsrv.ipu_iu
60
+ self.ipu_nm = hsrv.ipu_nm
59
61
  self.ipa_nm = hsrv.ipa_nm
60
62
  self.xff_nm = hsrv.xff_nm
61
63
  self.xff_lan = hsrv.xff_lan # type: ignore
copyparty/httpsrv.py CHANGED
@@ -69,6 +69,7 @@ from .util import (
69
69
  build_netmap,
70
70
  has_resource,
71
71
  ipnorm,
72
+ load_ipu,
72
73
  load_resource,
73
74
  min_ex,
74
75
  shut_socket,
@@ -171,6 +172,11 @@ class HttpSrv(object):
171
172
  self.j2 = {x: env.get_template(x + ".html") for x in jn}
172
173
  self.prism = has_resource(self.E, "web/deps/prism.js.gz")
173
174
 
175
+ if self.args.ipu:
176
+ self.ipu_iu, self.ipu_nm = load_ipu(self.log, self.args.ipu)
177
+ else:
178
+ self.ipu_iu = self.ipu_nm = None
179
+
174
180
  self.ipa_nm = build_netmap(self.args.ipa)
175
181
  self.xff_nm = build_netmap(self.args.xff_src)
176
182
  self.xff_lan = build_netmap("lan")
copyparty/mtag.py CHANGED
@@ -467,7 +467,7 @@ class MTag(object):
467
467
  sv = str(zv).split("/")[0].strip().lstrip("0")
468
468
  ret[sk] = sv or 0
469
469
 
470
- # normalize key notation to rkeobo
470
+ # normalize key notation to rekobo
471
471
  okey = ret.get("key")
472
472
  if okey:
473
473
  key = str(okey).replace(" ", "").replace("maj", "").replace("min", "m")
copyparty/ssdp.py CHANGED
@@ -80,7 +80,7 @@ class SSDPr(object):
80
80
  name = self.args.doctitle
81
81
  zs = zs.strip().format(c(ubase), c(url), c(name), c(self.args.zsid))
82
82
  hc.reply(zs.encode("utf-8", "replace"))
83
- return False # close connectino
83
+ return False # close connection
84
84
 
85
85
 
86
86
  class SSDPd(MCast):
copyparty/svchub.py CHANGED
@@ -54,6 +54,7 @@ from .util import (
54
54
  alltrace,
55
55
  ansi_re,
56
56
  build_netmap,
57
+ load_ipu,
57
58
  min_ex,
58
59
  mp,
59
60
  odfusion,
@@ -215,6 +216,11 @@ class SvcHub(object):
215
216
  noch.update([x for x in zsl if x])
216
217
  args.chpw_no = noch
217
218
 
219
+ if args.ipu:
220
+ iu, nm = load_ipu(self.log, args.ipu)
221
+ setattr(args, "ipu_iu", iu)
222
+ setattr(args, "ipu_nm", nm)
223
+
218
224
  if not self.args.no_ses:
219
225
  self.setup_session_db()
220
226
 
copyparty/tcpsrv.py CHANGED
@@ -92,7 +92,7 @@ class TcpSrv(object):
92
92
  continue
93
93
 
94
94
  # binding 0.0.0.0 after :: fails on dualstack
95
- # but is necessary on non-dualstakc
95
+ # but is necessary on non-dualstack
96
96
  if successful_binds:
97
97
  continue
98
98