copyparty 1.16.1__py3-none-any.whl → 1.16.3__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
@@ -1116,6 +1116,8 @@ def add_zc_mdns(ap):
1116
1116
  ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
1117
1117
  ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
1118
1118
  ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
1119
+ ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)")
1120
+ ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)")
1119
1121
  ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
1120
1122
  ap2.add_argument("--zm-ld", metavar="PATH", type=u, default="", help="link a specific folder for webdav shares")
1121
1123
  ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares")
@@ -1308,7 +1310,7 @@ def add_logging(ap):
1308
1310
  ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
1309
1311
  ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
1310
1312
  ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
1311
- ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
1313
+ ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
1312
1314
 
1313
1315
 
1314
1316
  def add_admin(ap):
@@ -1393,6 +1395,7 @@ def add_db_general(ap, hcores):
1393
1395
  ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
1394
1396
  ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
1395
1397
  ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
1398
+ ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
1396
1399
  ap2.add_argument("--dotsrch", action="store_true", help="show dotfiles in search results (volflags: dotsrch | nodotsrch)")
1397
1400
 
1398
1401
 
@@ -1449,6 +1452,8 @@ def add_ui(ap, retry):
1449
1452
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
1450
1453
  ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
1451
1454
  ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
1455
+ ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
1456
+ ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1452
1457
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1453
1458
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1454
1459
  ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 1)
3
+ VERSION = (1, 16, 3)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2024, 11, 15)
5
+ BUILD_DT = (2024, 12, 4)
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
@@ -360,6 +360,8 @@ class VFS(object):
360
360
  self.ahtml = {}
361
361
  self.aadmin = {}
362
362
  self.adot = {}
363
+ self.js_ls = {}
364
+ self.js_htm = ""
363
365
 
364
366
  if realpath:
365
367
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
@@ -1871,6 +1873,7 @@ class AuthSrv(object):
1871
1873
  ["no_hash", "nohash"],
1872
1874
  ["no_idx", "noidx"],
1873
1875
  ["og_ua", "og_ua"],
1876
+ ["srch_excl", "srch_excl"],
1874
1877
  ]:
1875
1878
  if vf in vol.flags:
1876
1879
  ptn = re.compile(vol.flags.pop(vf))
@@ -2077,6 +2080,22 @@ class AuthSrv(object):
2077
2080
  self.log(t.format(mtp), 1)
2078
2081
  errors = True
2079
2082
 
2083
+ for vol in vfs.all_vols.values():
2084
+ re1 = vol.flags.get("srch_excl")
2085
+ excl = [re1.pattern] if re1 else []
2086
+
2087
+ vpaths = []
2088
+ vtop = vol.vpath
2089
+ for vp2 in vfs.all_vols.keys():
2090
+ if vp2.startswith((vtop + "/").lstrip("/")) and vtop != vp2:
2091
+ vpaths.append(re.escape(vp2[len(vtop) :].lstrip("/")))
2092
+ if vpaths:
2093
+ excl.append("^(%s)/" % ("|".join(vpaths),))
2094
+
2095
+ vol.flags["srch_re_dots"] = re.compile("|".join(excl or ["^$"]))
2096
+ excl.extend([r"^\.", r"/\."])
2097
+ vol.flags["srch_re_nodot"] = re.compile("|".join(excl))
2098
+
2080
2099
  have_daw = False
2081
2100
  for vol in vfs.all_nodes.values():
2082
2101
  daw = vol.flags.get("daw") or self.args.daw
@@ -2297,6 +2316,70 @@ class AuthSrv(object):
2297
2316
  cur.close()
2298
2317
  db.close()
2299
2318
 
2319
+ self.js_ls = {}
2320
+ self.js_htm = {}
2321
+ for vn in self.vfs.all_nodes.values():
2322
+ vf = vn.flags
2323
+ vn.js_ls = {
2324
+ "idx": "e2d" in vf,
2325
+ "itag": "e2t" in vf,
2326
+ "dnsort": "nsort" in vf,
2327
+ "dhsortn": vf["hsortn"],
2328
+ "dsort": vf["sort"],
2329
+ "dcrop": vf["crop"],
2330
+ "dth3x": vf["th3x"],
2331
+ "u2ts": vf["u2ts"],
2332
+ "frand": bool(vf.get("rand")),
2333
+ "lifetime": vf.get("lifetime") or 0,
2334
+ "unlist": vf.get("unlist") or "",
2335
+ }
2336
+ js_htm = {
2337
+ "s_name": self.args.bname,
2338
+ "have_up2k_idx": "e2d" in vf,
2339
+ "have_acode": not self.args.no_acode,
2340
+ "have_shr": self.args.shr,
2341
+ "have_zip": not self.args.no_zip,
2342
+ "have_mv": not self.args.no_mv,
2343
+ "have_del": not self.args.no_del,
2344
+ "have_unpost": int(self.args.unpost),
2345
+ "have_emp": self.args.emp,
2346
+ "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2347
+ "txt_ext": self.args.textfiles.replace(",", " "),
2348
+ "def_hcols": list(vf.get("mth") or []),
2349
+ "unlist0": vf.get("unlist") or "",
2350
+ "dgrid": "grid" in vf,
2351
+ "dgsel": "gsel" in vf,
2352
+ "dnsort": "nsort" in vf,
2353
+ "dhsortn": vf["hsortn"],
2354
+ "dsort": vf["sort"],
2355
+ "dcrop": vf["crop"],
2356
+ "dth3x": vf["th3x"],
2357
+ "dvol": self.args.au_vol,
2358
+ "idxh": int(self.args.ih),
2359
+ "themes": self.args.themes,
2360
+ "turbolvl": self.args.turbo,
2361
+ "u2j": self.args.u2j,
2362
+ "u2sz": self.args.u2sz,
2363
+ "u2ts": vf["u2ts"],
2364
+ "frand": bool(vf.get("rand")),
2365
+ "lifetime": vn.js_ls["lifetime"],
2366
+ "u2sort": self.args.u2sort,
2367
+ }
2368
+ vn.js_htm = json.dumps(js_htm)
2369
+
2370
+ vols = list(vfs.all_nodes.values())
2371
+ if enshare:
2372
+ vols.append(shv)
2373
+ vols.extend(list(shv.nodes.values()))
2374
+
2375
+ for vol in vols:
2376
+ dbv = vol.get_dbv("")[0]
2377
+ vol.js_ls = vol.js_ls or dbv.js_ls or {}
2378
+ vol.js_htm = vol.js_htm or dbv.js_htm or "{}"
2379
+
2380
+ zs = str(vol.flags.get("tcolor") or self.args.tcolor)
2381
+ vol.flags["tcolor"] = zs.lstrip("#")
2382
+
2300
2383
  def load_sessions(self, quiet=False) :
2301
2384
  # mutex me
2302
2385
  if self.args.no_ses:
copyparty/cfg.py CHANGED
@@ -42,6 +42,7 @@ def vf_bmap() :
42
42
  "magic",
43
43
  "no_sb_md",
44
44
  "no_sb_lg",
45
+ "nsort",
45
46
  "og",
46
47
  "og_no_head",
47
48
  "og_s_title",
@@ -69,6 +70,7 @@ def vf_vmap() :
69
70
  }
70
71
  for k in (
71
72
  "dbd",
73
+ "hsortn",
72
74
  "html_head",
73
75
  "lg_sbf",
74
76
  "md_sbf",
@@ -190,6 +192,7 @@ flagcats = {
190
192
  "xvol": "do not follow symlinks leaving the volume root",
191
193
  "dotsrch": "show dotfiles in search results",
192
194
  "nodotsrch": "hide dotfiles in search results (default)",
195
+ "srch_excl": "exclude search results with URL matching this regex",
193
196
  },
194
197
  'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
195
198
  "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
copyparty/httpcli.py CHANGED
@@ -14,6 +14,7 @@ import re
14
14
  import socket
15
15
  import stat
16
16
  import string
17
+ import sys
17
18
  import threading # typechk
18
19
  import time
19
20
  import uuid
@@ -76,6 +77,7 @@ from .util import (
76
77
  html_escape,
77
78
  humansize,
78
79
  ipnorm,
80
+ justcopy,
79
81
  load_resource,
80
82
  loadpy,
81
83
  log_reloc,
@@ -120,6 +122,8 @@ if not hasattr(socket, "AF_UNIX"):
120
122
 
121
123
  _ = (argparse, threading)
122
124
 
125
+ USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
126
+
123
127
  NO_CACHE = {"Cache-Control": "no-cache"}
124
128
 
125
129
  ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
@@ -133,6 +137,10 @@ READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
133
137
 
134
138
  RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
135
139
 
140
+ A_FILE = os.stat_result(
141
+ (0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
142
+ )
143
+
136
144
 
137
145
  class HttpCli(object):
138
146
  """
@@ -243,7 +251,6 @@ class HttpCli(object):
243
251
  ka["ts"] = self.conn.hsrv.cachebuster()
244
252
  ka["lang"] = self.args.lang
245
253
  ka["favico"] = self.args.favico
246
- ka["s_name"] = self.args.bname
247
254
  ka["s_doctitle"] = self.args.doctitle
248
255
  ka["tcolor"] = self.vn.flags["tcolor"]
249
256
 
@@ -700,7 +707,7 @@ class HttpCli(object):
700
707
 
701
708
  if pex.code != 404 or self.do_log:
702
709
  self.log(
703
- "%s\033[0m, %s" % (msg, self.vpath),
710
+ "http%d: %s\033[0m, %s" % (pex.code, msg, self.vpath),
704
711
  6 if em.startswith("client d/c ") else 3,
705
712
  )
706
713
 
@@ -1239,7 +1246,7 @@ class HttpCli(object):
1239
1246
  self.log("RSS %s @%s" % (self.req, self.uname))
1240
1247
 
1241
1248
  if not self.can_read:
1242
- return self.tx_404()
1249
+ return self.tx_404(True)
1243
1250
 
1244
1251
  vn = self.vn
1245
1252
  if not vn.flags.get("rss"):
@@ -1409,17 +1416,19 @@ class HttpCli(object):
1409
1416
  vst = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi))
1410
1417
 
1411
1418
  try:
1412
- topdir = {"vp": "", "st": bos.stat(tap)}
1419
+ st = bos.stat(tap)
1413
1420
  except OSError as ex:
1414
1421
  if ex.errno not in (errno.ENOENT, errno.ENOTDIR):
1415
1422
  raise
1416
1423
  raise Pebkac(404)
1417
1424
 
1425
+ topdir = {"vp": "", "st": st}
1418
1426
  fgen = []
1419
1427
 
1420
1428
  depth = self.headers.get("depth", "infinity").lower()
1421
1429
  if depth == "infinity":
1422
- if not self.can_read:
1430
+ # allow depth:0 from unmapped root, but require read-axs otherwise
1431
+ if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
1423
1432
  t = "depth:infinity requires read-access in /%s"
1424
1433
  t = t % (self.vpath,)
1425
1434
  self.log(t, 3)
@@ -1450,6 +1459,12 @@ class HttpCli(object):
1450
1459
  wrap=False,
1451
1460
  )
1452
1461
 
1462
+ elif depth == "0" or not stat.S_ISDIR(st.st_mode):
1463
+ # propfind on a file; return as topdir
1464
+ if not self.can_read and not self.can_get:
1465
+ self.log("inaccessible: [%s]" % (self.vpath,))
1466
+ raise Pebkac(401, "authenticate")
1467
+
1453
1468
  elif depth == "1":
1454
1469
  _, vfs_ls, vfs_virt = vn.ls(
1455
1470
  rem,
@@ -1468,15 +1483,12 @@ class HttpCli(object):
1468
1483
  fgen = [{"vp": vp, "st": st} for vp, st in vfs_ls]
1469
1484
  fgen += [{"vp": v, "st": vst} for v in vfs_virt]
1470
1485
 
1471
- elif depth == "0":
1472
- pass
1473
-
1474
1486
  else:
1475
1487
  t = "invalid depth value '{}' (must be either '0' or '1'{})"
1476
1488
  t2 = " or 'infinity'" if self.args.dav_inf else ""
1477
1489
  raise Pebkac(412, t.format(depth, t2))
1478
1490
 
1479
- if not self.can_read and not self.can_write and not self.can_get and not fgen:
1491
+ if not self.can_read and not self.can_write and not fgen:
1480
1492
  self.log("inaccessible: [%s]" % (self.vpath,))
1481
1493
  raise Pebkac(401, "authenticate")
1482
1494
 
@@ -1755,7 +1767,7 @@ class HttpCli(object):
1755
1767
 
1756
1768
  if not self.can_write:
1757
1769
  t = "user %s does not have write-access under /%s"
1758
- raise Pebkac(403, t % (self.uname, self.vn.vpath))
1770
+ raise Pebkac(403 if self.pw else 401, t % (self.uname, self.vn.vpath))
1759
1771
 
1760
1772
  if not self.args.no_dav and self._applesan():
1761
1773
  return self.headers.get("content-length") == "0"
@@ -2046,10 +2058,31 @@ class HttpCli(object):
2046
2058
  # small toctou, but better than clobbering a hardlink
2047
2059
  wunlink(self.log, path, vfs.flags)
2048
2060
 
2061
+ hasher = None
2062
+ copier = hashcopy
2063
+ if "ck" in self.ouparam or "ck" in self.headers:
2064
+ zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2065
+ if not zs or zs == "no":
2066
+ copier = justcopy
2067
+ elif zs == "md5":
2068
+ hasher = hashlib.md5(**USED4SEC)
2069
+ elif zs == "sha1":
2070
+ hasher = hashlib.sha1(**USED4SEC)
2071
+ elif zs == "sha256":
2072
+ hasher = hashlib.sha256(**USED4SEC)
2073
+ elif zs in ("blake2", "b2"):
2074
+ hasher = hashlib.blake2b(**USED4SEC)
2075
+ elif zs in ("blake2s", "b2s"):
2076
+ hasher = hashlib.blake2s(**USED4SEC)
2077
+ elif zs == "sha512":
2078
+ pass
2079
+ else:
2080
+ raise Pebkac(500, "unknown hash alg")
2081
+
2049
2082
  f, fn = ren_open(fn, *open_a, **params)
2050
2083
  try:
2051
2084
  path = os.path.join(fdir, fn)
2052
- post_sz, sha_hex, sha_b64 = hashcopy(reader, f, None, 0, self.args.s_wr_slp)
2085
+ post_sz, sha_hex, sha_b64 = copier(reader, f, hasher, 0, self.args.s_wr_slp)
2053
2086
  finally:
2054
2087
  f.close()
2055
2088
 
@@ -2286,8 +2319,8 @@ class HttpCli(object):
2286
2319
  # kinda silly but has the least side effects
2287
2320
  return self.handle_new_md()
2288
2321
 
2289
- if act == "bput":
2290
- return self.handle_plain_upload(file0)
2322
+ if act in ("bput", "uput"):
2323
+ return self.handle_plain_upload(file0, act == "uput")
2291
2324
 
2292
2325
  if act == "tput":
2293
2326
  return self.handle_text_upload()
@@ -2898,13 +2931,41 @@ class HttpCli(object):
2898
2931
  )
2899
2932
 
2900
2933
  def handle_plain_upload(
2901
- self, file0
2934
+ self,
2935
+ file0 ,
2936
+ nohash ,
2902
2937
  ) :
2903
2938
  assert self.parser
2904
2939
  nullwrite = self.args.nw
2905
2940
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2906
2941
  self._assert_safe_rem(rem)
2907
2942
 
2943
+ halg = "sha512"
2944
+ hasher = None
2945
+ copier = hashcopy
2946
+ if nohash:
2947
+ halg = ""
2948
+ copier = justcopy
2949
+ elif "ck" in self.ouparam or "ck" in self.headers:
2950
+ halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
2951
+ if not halg or halg == "no":
2952
+ copier = justcopy
2953
+ halg = ""
2954
+ elif halg == "md5":
2955
+ hasher = hashlib.md5(**USED4SEC)
2956
+ elif halg == "sha1":
2957
+ hasher = hashlib.sha1(**USED4SEC)
2958
+ elif halg == "sha256":
2959
+ hasher = hashlib.sha256(**USED4SEC)
2960
+ elif halg in ("blake2", "b2"):
2961
+ hasher = hashlib.blake2b(**USED4SEC)
2962
+ elif halg in ("blake2s", "b2s"):
2963
+ hasher = hashlib.blake2s(**USED4SEC)
2964
+ elif halg == "sha512":
2965
+ pass
2966
+ else:
2967
+ raise Pebkac(500, "unknown hash alg")
2968
+
2908
2969
  upload_vpath = self.vpath
2909
2970
  lim = vfs.get_dbv(rem)[0].lim
2910
2971
  fdir_base = vfs.canonical(rem)
@@ -3034,8 +3095,8 @@ class HttpCli(object):
3034
3095
  try:
3035
3096
  tabspath = os.path.join(fdir, tnam)
3036
3097
  self.log("writing to {}".format(tabspath))
3037
- sz, sha_hex, sha_b64 = hashcopy(
3038
- p_data, f, None, max_sz, self.args.s_wr_slp
3098
+ sz, sha_hex, sha_b64 = copier(
3099
+ p_data, f, hasher, max_sz, self.args.s_wr_slp
3039
3100
  )
3040
3101
  if sz == 0:
3041
3102
  raise Pebkac(400, "empty files in post")
@@ -3167,10 +3228,15 @@ class HttpCli(object):
3167
3228
  jmsg["error"] = errmsg
3168
3229
  errmsg = "ERROR: " + errmsg
3169
3230
 
3231
+ if halg:
3232
+ file_fmt = '{0}: {1} // {2} // {3} bytes // <a href="/{4}">{5}</a> {6}\n'
3233
+ else:
3234
+ file_fmt = '{3} bytes // <a href="/{4}">{5}</a> {6}\n'
3235
+
3170
3236
  for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
3171
3237
  vsuf = ""
3172
3238
  if (self.can_read or self.can_upget) and "fk" in vfs.flags:
3173
- st = bos.stat(ap)
3239
+ st = A_FILE if nullwrite else bos.stat(ap)
3174
3240
  alg = 2 if "fka" in vfs.flags else 1
3175
3241
  vsuf = "?k=" + self.gen_fk(
3176
3242
  alg,
@@ -3185,7 +3251,8 @@ class HttpCli(object):
3185
3251
 
3186
3252
  vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
3187
3253
  rel_url = quotep(self.args.RS + vpath) + vsuf
3188
- msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
3254
+ msg += file_fmt.format(
3255
+ halg,
3189
3256
  sha_hex[:56],
3190
3257
  sha_b64,
3191
3258
  sz,
@@ -3201,13 +3268,14 @@ class HttpCli(object):
3201
3268
  self.host,
3202
3269
  rel_url,
3203
3270
  ),
3204
- "sha512": sha_hex[:56],
3205
- "sha_b64": sha_b64,
3206
3271
  "sz": sz,
3207
3272
  "fn": lfn,
3208
3273
  "fn_orig": ofn,
3209
3274
  "path": rel_url,
3210
3275
  }
3276
+ if halg:
3277
+ jpart[halg] = sha_hex[:56]
3278
+ jpart["sha_b64"] = sha_b64
3211
3279
  jmsg["files"].append(jpart)
3212
3280
 
3213
3281
  vspd = self._spd(sz_total, False)
@@ -4600,6 +4668,18 @@ class HttpCli(object):
4600
4668
  if "th" in self.ouparam:
4601
4669
  return self.tx_svg("e" + pt[:3])
4602
4670
 
4671
+ # most webdav clients will not send credentials until they
4672
+ # get 401'd, so send a challenge if we're Absolutely Sure
4673
+ # that the client is not a graphical browser
4674
+ if (
4675
+ rc == 403
4676
+ and not self.pw
4677
+ and not self.ua.startswith("Mozilla/")
4678
+ and "sec-fetch-site" not in self.headers
4679
+ ):
4680
+ rc = 401
4681
+ self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
4682
+
4603
4683
  t = t.format(self.args.SR)
4604
4684
  qv = quotep(self.vpaths) + self.ourlq()
4605
4685
  html = self.j2s(
@@ -5240,7 +5320,7 @@ class HttpCli(object):
5240
5320
  st = bos.stat(abspath)
5241
5321
  except:
5242
5322
  if "on404" not in vn.flags:
5243
- return self.tx_404()
5323
+ return self.tx_404(not self.can_read)
5244
5324
 
5245
5325
  ret = self.on40x(vn.flags["on404"], vn, rem)
5246
5326
  if ret == "true":
@@ -5251,9 +5331,9 @@ class HttpCli(object):
5251
5331
  try:
5252
5332
  st = bos.stat(abspath)
5253
5333
  except:
5254
- return self.tx_404()
5334
+ return self.tx_404(not self.can_read)
5255
5335
  else:
5256
- return self.tx_404()
5336
+ return self.tx_404(not self.can_read)
5257
5337
 
5258
5338
  if rem.startswith(".hist/up2k.") or (
5259
5339
  rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
@@ -5380,13 +5460,13 @@ class HttpCli(object):
5380
5460
  vrem = vjoin(vrem, fn)
5381
5461
  abspath = ap2
5382
5462
  break
5383
- elif self.vpath.rsplit("/", 1)[1] in ("index.htm", "index.html"):
5463
+ elif self.vpath.rsplit("/", 1)[-1] in ("index.htm", "index.html"):
5384
5464
  fk_pass = True
5385
5465
 
5386
5466
  if not is_dir and (self.can_read or self.can_get):
5387
5467
  if not self.can_read and not fk_pass and "fk" in vn.flags:
5388
5468
  if not use_filekey:
5389
- return self.tx_404()
5469
+ return self.tx_404(True)
5390
5470
 
5391
5471
  if add_og and not abspath.lower().endswith(".md"):
5392
5472
  if og_ua or self.host not in self.headers.get("referer", ""):
@@ -5474,61 +5554,28 @@ class HttpCli(object):
5474
5554
  is_js = False
5475
5555
 
5476
5556
  vf = vn.flags
5477
- unlist = vf.get("unlist", "")
5478
5557
  ls_ret = {
5479
5558
  "dirs": [],
5480
5559
  "files": [],
5481
5560
  "taglist": [],
5482
5561
  "srvinf": srv_infot,
5483
5562
  "acct": self.uname,
5484
- "idx": e2d,
5485
- "itag": e2t,
5486
- "dsort": vf["sort"],
5487
- "dcrop": vf["crop"],
5488
- "dth3x": vf["th3x"],
5489
- "u2ts": vf["u2ts"],
5490
- "lifetime": vn.flags.get("lifetime") or 0,
5491
- "frand": bool(vn.flags.get("rand")),
5492
- "unlist": unlist,
5493
5563
  "perms": perms,
5564
+ "cfg": vn.js_ls,
5494
5565
  }
5495
5566
  cgv = {
5496
5567
  "ls0": None,
5497
5568
  "acct": self.uname,
5498
5569
  "perms": perms,
5499
- "u2ts": vf["u2ts"],
5500
- "lifetime": ls_ret["lifetime"],
5501
- "frand": bool(vn.flags.get("rand")),
5502
- "def_hcols": [],
5503
- "have_emp": self.args.emp,
5504
- "have_up2k_idx": e2d,
5505
- "have_acode": (not self.args.no_acode),
5506
- "have_mv": (not self.args.no_mv),
5507
- "have_del": (not self.args.no_del),
5508
- "have_zip": (not self.args.no_zip),
5509
- "have_shr": self.args.shr,
5510
- "have_unpost": int(self.args.unpost),
5511
- "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
5512
- "dgrid": "grid" in vf,
5513
- "dgsel": "gsel" in vf,
5514
- "dsort": vf["sort"],
5515
- "dcrop": vf["crop"],
5516
- "dth3x": vf["th3x"],
5517
- "dvol": self.args.au_vol,
5518
- "themes": self.args.themes,
5519
- "turbolvl": self.args.turbo,
5520
- "u2j": self.args.u2j,
5521
- "u2sz": self.args.u2sz,
5522
- "idxh": int(self.args.ih),
5523
- "u2sort": self.args.u2sort,
5524
5570
  }
5525
5571
  j2a = {
5572
+ "cgv1": vn.js_htm,
5526
5573
  "cgv": cgv,
5527
5574
  "vpnodes": vpnodes,
5528
5575
  "files": [],
5529
5576
  "ls0": None,
5530
5577
  "taglist": [],
5531
- "have_tags_idx": e2t,
5578
+ "have_tags_idx": int(e2t),
5532
5579
  "have_b_u": (self.can_write and self.uparam.get("b") == "u"),
5533
5580
  "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
5534
5581
  "url_suf": url_suf,
@@ -5899,17 +5946,12 @@ class HttpCli(object):
5899
5946
  "dirs": dirs,
5900
5947
  "files": files,
5901
5948
  "taglist": taglist,
5902
- "unlist": unlist,
5903
5949
  }
5904
5950
  j2a["files"] = []
5905
5951
  else:
5906
5952
  j2a["files"] = dirs + files
5907
5953
 
5908
5954
  j2a["taglist"] = taglist
5909
- j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
5910
-
5911
- if "mth" in vn.flags:
5912
- j2a["def_hcols"] = list(vn.flags["mth"])
5913
5955
 
5914
5956
  if add_og and "raw" not in self.uparam:
5915
5957
  j2a["this"] = self
copyparty/httpsrv.py CHANGED
@@ -132,7 +132,7 @@ class HttpSrv(object):
132
132
  dls = {} # state
133
133
  self.dli = self.tdli = dli
134
134
  self.dls = self.tdls = dls
135
- self.iiam = '<img src="%s.cpr/iiam.gif" />' % (self.args.SRS,)
135
+ self.iiam = '<img src="%s.cpr/iiam.gif?cache=i" />' % (self.args.SRS,)
136
136
 
137
137
  self.bound = set()
138
138
  self.name = "hsrv" + nsuf
copyparty/mdns.py CHANGED
@@ -25,6 +25,7 @@ from .stolen.dnslib import (
25
25
  DNSHeader,
26
26
  DNSQuestion,
27
27
  DNSRecord,
28
+ set_avahi_379,
28
29
  )
29
30
  from .util import CachedSet, Daemon, Netdev, list_ips, min_ex
30
31
 
@@ -68,6 +69,9 @@ class MDNS(MCast):
68
69
  self.ngen = ngen
69
70
  self.ttl = 300
70
71
 
72
+ if not self.args.zm_nwa_1:
73
+ set_avahi_379()
74
+
71
75
  zs = self.args.name + ".local."
72
76
  zs = zs.encode("ascii", "replace").decode("ascii", "replace")
73
77
  self.hn = "-".join(x for x in zs.split("?") if x) or (
@@ -332,6 +336,9 @@ class MDNS(MCast):
332
336
  self.log("stopped", 2)
333
337
  return
334
338
 
339
+ if self.args.zm_no_pe:
340
+ continue
341
+
335
342
  t = "{} {} \033[33m|{}| {}\n{}".format(
336
343
  self.srv[sck].name, addr, len(buf), repr(buf)[2:-1], min_ex()
337
344
  )