copyparty 1.18.2__py3-none-any.whl → 1.18.4__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
@@ -855,6 +855,43 @@ def get_sects():
855
855
  """
856
856
  ),
857
857
  ],
858
+ [
859
+ "chmod",
860
+ "file/folder permissions",
861
+ dedent(
862
+ """
863
+ global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
864
+
865
+ similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
866
+
867
+ the value is a three-digit octal number such as 755, 750, 644, etc.
868
+
869
+ first digit = "User"; permission for the unix-user
870
+ second digit = "Group"; permission for the unix-group
871
+ third digit = "Other"; permission for all other users/groups
872
+
873
+ for files:
874
+ 0 = --- = no access
875
+ 1 = --x = can execute the file as a program
876
+ 2 = -w- = can write
877
+ 3 = -wx = can write and execute
878
+ 4 = r-- = can read
879
+ 5 = r-x = can read and execute
880
+ 6 = rw- = can read and write
881
+ 7 = rwx = can read, write, execute
882
+
883
+ for directories/folders:
884
+ 0 = --- = no access
885
+ 1 = --x = can read files in folder but not list contents
886
+ 2 = -w- = n/a
887
+ 3 = -wx = can create files but not list
888
+ 4 = r-- = can list, but not read/write
889
+ 5 = r-x = can list and read files
890
+ 6 = rw- = n/a
891
+ 7 = rwx = can read, write, list
892
+ """
893
+ ),
894
+ ],
858
895
  [
859
896
  "pwhash",
860
897
  "password hashing",
@@ -1005,6 +1042,8 @@ def add_upload(ap):
1005
1042
  ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
1006
1043
  ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
1007
1044
  ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
1045
+ ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
1046
+ ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
1008
1047
  ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
1009
1048
  ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
1010
1049
  ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
@@ -1041,7 +1080,7 @@ def add_network(ap):
1041
1080
  ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]")
1042
1081
  if ANYWIN:
1043
1082
  ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
1044
- else:
1083
+ elif not MACOS:
1045
1084
  ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
1046
1085
  ap2.add_argument("--wr-h-eps", metavar="PATH", type=u, default="", help="write list of listening-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
1047
1086
  ap2.add_argument("--wr-h-aon", metavar="PATH", type=u, default="", help="write list of accessible-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
@@ -1503,6 +1542,7 @@ def add_ui(ap, retry):
1503
1542
  ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
1504
1543
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1505
1544
  ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
1545
+ ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
1506
1546
  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)")
1507
1547
  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")
1508
1548
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 2)
3
+ VERSION = (1, 18, 4)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 7)
5
+ BUILD_DT = (2025, 7, 25)
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
@@ -113,6 +113,8 @@ class Lim(object):
113
113
 
114
114
  self.reg = None # up2k registry
115
115
 
116
+ self.chmod_d = 0o755
117
+
116
118
  self.nups = {} # num tracker
117
119
  self.bups = {} # byte tracker list
118
120
  self.bupc = {} # byte tracker cache
@@ -273,7 +275,7 @@ class Lim(object):
273
275
  if not dirs:
274
276
  # no branches yet; make one
275
277
  sub = os.path.join(path, "0")
276
- bos.mkdir(sub)
278
+ bos.mkdir(sub, self.chmod_d)
277
279
  else:
278
280
  # try newest branch only
279
281
  sub = os.path.join(path, str(dirs[-1]))
@@ -288,7 +290,7 @@ class Lim(object):
288
290
 
289
291
  # make a branch
290
292
  sub = os.path.join(path, str(dirs[-1] + 1))
291
- bos.mkdir(sub)
293
+ bos.mkdir(sub, self.chmod_d)
292
294
  ret = self.dive(sub, lvs - 1)
293
295
  if ret is None:
294
296
  raise Pebkac(500, "rotation bug")
@@ -365,6 +367,7 @@ class VFS(object):
365
367
  self.shr_src = None # source vfs+rem of a share
366
368
  self.shr_files = set() # filenames to include from shr_src
367
369
  self.shr_owner = "" # uname
370
+ self.shr_all_aps = []
368
371
  self.aread = {}
369
372
  self.awrite = {}
370
373
  self.amove = {}
@@ -376,20 +379,20 @@ class VFS(object):
376
379
  self.adot = {}
377
380
  self.js_ls = {}
378
381
  self.js_htm = ""
382
+ self.all_vols = {} # flattened recursive
383
+ self.all_nodes = {} # also jumpvols/shares
379
384
 
380
385
  if realpath:
381
386
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
382
387
  vp = vpath + ("/" if vpath else "")
383
388
  self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
384
389
  self.dbpath = self.histpath
385
- self.all_vols = {vpath: self} # flattened recursive
386
- self.all_nodes = {vpath: self} # also jumpvols/shares
387
- self.all_aps = [(rp, self)]
390
+ self.all_vols[vpath] = self
391
+ self.all_nodes[vpath] = self
392
+ self.all_aps = [(rp, [self])]
388
393
  self.all_vps = [(vp, self)]
389
394
  else:
390
395
  self.histpath = self.dbpath = ""
391
- self.all_vols = {}
392
- self.all_nodes = {}
393
396
  self.all_aps = []
394
397
  self.all_vps = []
395
398
 
@@ -417,7 +420,11 @@ class VFS(object):
417
420
  rp = self.realpath
418
421
  rp += "" if rp.endswith(os.sep) else os.sep
419
422
  vp = self.vpath + ("/" if self.vpath else "")
420
- aps.append((rp, self))
423
+ hit = next((x[1] for x in aps if x[0] == rp), None)
424
+ if hit:
425
+ hit.append(self)
426
+ else:
427
+ aps.append((rp, [self]))
421
428
  vps.append((vp, self))
422
429
 
423
430
  for v in self.nodes.values():
@@ -841,9 +848,11 @@ class VFS(object):
841
848
  return None
842
849
 
843
850
  if "xvol" in self.flags:
844
- for vap, vn in self.root.all_aps:
851
+ all_aps = self.shr_all_aps or self.root.all_aps
852
+
853
+ for vap, vns in all_aps:
845
854
  if aps.startswith(vap):
846
- return vn
855
+ return self if self in vns else vns[0]
847
856
 
848
857
  if self.log:
849
858
  self.log("vfs", "xvol: %r" % (ap,), 3)
@@ -852,6 +861,53 @@ class VFS(object):
852
861
 
853
862
  return self
854
863
 
864
+ def check_landmarks(self) :
865
+ if self.dbv:
866
+ return True
867
+
868
+ vps = self.flags.get("landmark") or []
869
+ if not vps:
870
+ return True
871
+
872
+ failed = ""
873
+ for vp in vps:
874
+ if "^=" in vp:
875
+ vp, zs = vp.split("^=", 1)
876
+ expect = zs.encode("utf-8")
877
+ else:
878
+ expect = b""
879
+
880
+ if self.log:
881
+ t = "checking [/%s] landmark [%s]"
882
+ self.log("vfs", t % (self.vpath, vp), 6)
883
+
884
+ ap = "?"
885
+ try:
886
+ ap = self.canonical(vp)
887
+ with open(ap, "rb") as f:
888
+ buf = f.read(4096)
889
+ if not buf.startswith(expect):
890
+ t = "file [%s] does not start with the expected bytes %s"
891
+ failed = t % (ap, expect)
892
+ break
893
+ except Exception as ex:
894
+ t = "%r while trying to read [%s] => [%s]"
895
+ failed = t % (ex, vp, ap)
896
+ break
897
+
898
+ if not failed:
899
+ return True
900
+
901
+ if self.log:
902
+ t = "WARNING: landmark verification failed; %s; will now disable up2k database for volume [/%s]"
903
+ self.log("vfs", t % (failed, self.vpath), 3)
904
+
905
+ for rm in "e2d e2t e2v".split():
906
+ self.flags = {k: v for k, v in self.flags.items() if not k.startswith(rm)}
907
+ self.flags["d2d"] = True
908
+ self.flags["d2t"] = True
909
+ return False
910
+
855
911
 
856
912
  if WINDOWS:
857
913
  re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
@@ -915,6 +971,9 @@ class AuthSrv(object):
915
971
 
916
972
  yield prev, True
917
973
 
974
+ def vf0(self):
975
+ return {"d2d": True, "tcolor": self.args.tcolor}
976
+
918
977
  def idp_checkin(
919
978
  self, broker , uname , gname
920
979
  ) :
@@ -1481,7 +1540,7 @@ class AuthSrv(object):
1481
1540
  flags[name] = True
1482
1541
  return
1483
1542
 
1484
- zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1543
+ zs = "ext_th landmark mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1485
1544
  if name not in zs.split():
1486
1545
  if value is True:
1487
1546
  t = "└─add volflag [{}] = {} ({})"
@@ -1620,13 +1679,12 @@ class AuthSrv(object):
1620
1679
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
1621
1680
  self.log(t, 1)
1622
1681
  axs = AXS()
1623
- vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1682
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1624
1683
  if not axs.uread:
1625
1684
  self.badcfg1 = True
1626
1685
  elif "" not in mount:
1627
1686
  # there's volumes but no root; make root inaccessible
1628
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1629
- vfs = VFS(self.log_func, "", "", "", AXS(), zsd)
1687
+ vfs = VFS(self.log_func, "", "", "", AXS(), self.vf0())
1630
1688
 
1631
1689
  maxdepth = 0
1632
1690
  for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
@@ -1674,8 +1732,7 @@ class AuthSrv(object):
1674
1732
  shrs = enshare[1:]
1675
1733
  if enshare:
1676
1734
 
1677
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1678
- shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
1735
+ shv = VFS(self.log_func, "", shr, shr, AXS(), self.vf0())
1679
1736
 
1680
1737
  db_path = self.args.shr_db
1681
1738
  db = sqlite3.connect(db_path)
@@ -2057,6 +2114,7 @@ class AuthSrv(object):
2057
2114
 
2058
2115
  all_mte = {}
2059
2116
  errors = False
2117
+ free_umask = False
2060
2118
  for vol in vfs.all_nodes.values():
2061
2119
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2062
2120
  vol.flags["e2ds"] = True
@@ -2113,6 +2171,27 @@ class AuthSrv(object):
2113
2171
  t = 'volume "/%s" has invalid %stry [%s]'
2114
2172
  raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
2115
2173
 
2174
+ for k in ("chmod_d", "chmod_f"):
2175
+ is_d = k == "chmod_d"
2176
+ zs = vol.flags.get(k, "")
2177
+ if not zs and is_d:
2178
+ zs = "755"
2179
+ if not zs:
2180
+ vol.flags.pop(k, None)
2181
+ continue
2182
+ if not re.match("^[0-7]{3}$", zs):
2183
+ t = "config-option '%s' must be a three-digit octal value such as [755] or [644] but the value was [%s]"
2184
+ t = t % (k, zs)
2185
+ self.log(t, 1)
2186
+ raise Exception(t)
2187
+ zi = int(zs, 8)
2188
+ vol.flags[k] = zi
2189
+ if (is_d and zi != 0o755) or not is_d:
2190
+ free_umask = True
2191
+
2192
+ if vol.lim:
2193
+ vol.lim.chmod_d = vol.flags["chmod_d"]
2194
+
2116
2195
  if vol.flags.get("og"):
2117
2196
  self.args.uqe = True
2118
2197
 
@@ -2196,6 +2275,8 @@ class AuthSrv(object):
2196
2275
  t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
2197
2276
  self.log(t % (vol.vpath, etv), 3)
2198
2277
 
2278
+ vol.check_landmarks()
2279
+
2199
2280
  # d2d drops all database features for a volume
2200
2281
  for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
2201
2282
  if not vol.flags.get(grp, False):
@@ -2342,6 +2423,10 @@ class AuthSrv(object):
2342
2423
  if errors:
2343
2424
  sys.exit(1)
2344
2425
 
2426
+ setattr(self.args, "free_umask", free_umask)
2427
+ if free_umask:
2428
+ os.umask(0)
2429
+
2345
2430
  vfs.bubble_flags()
2346
2431
 
2347
2432
  have_e2d = False
@@ -2544,6 +2629,28 @@ class AuthSrv(object):
2544
2629
  shn.shr_src = (s_vfs, s_rem)
2545
2630
  shn.realpath = s_vfs.canonical(s_rem)
2546
2631
 
2632
+ # root.all_aps doesn't include any shares, so make a copy where the
2633
+ # share appears in all abspaths it can provide (for example for chk_ap)
2634
+ ap = shn.realpath
2635
+ if not ap.endswith(os.sep):
2636
+ ap += os.sep
2637
+ shn.shr_all_aps = [(x, y[:]) for x, y in vfs.all_aps]
2638
+ exact = False
2639
+ for ap2, vns in shn.shr_all_aps:
2640
+ if ap == ap2:
2641
+ exact = True
2642
+ if ap2.startswith(ap):
2643
+ try:
2644
+ vp2 = vjoin(s_rem, ap2[len(ap) :])
2645
+ vn2, _ = s_vfs.get(vp2, "*", False, False)
2646
+ if vn2 == s_vfs or vn2.dbv == s_vfs:
2647
+ vns.append(shn)
2648
+ except:
2649
+ pass
2650
+ if not exact:
2651
+ shn.shr_all_aps.append((ap, [shn]))
2652
+ shn.shr_all_aps.sort(key=lambda x: len(x[0]), reverse=True)
2653
+
2547
2654
  if self.args.shr_v:
2548
2655
  t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
2549
2656
  self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
@@ -2558,7 +2665,7 @@ class AuthSrv(object):
2558
2665
  continue # also fine
2559
2666
  for zs in svn.nodes.keys():
2560
2667
  # hide subvolume
2561
- vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), {})
2668
+ vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), self.vf0())
2562
2669
 
2563
2670
  cur2.close()
2564
2671
  cur.close()
@@ -2603,6 +2710,7 @@ class AuthSrv(object):
2603
2710
  "def_hcols": list(vf.get("mth") or []),
2604
2711
  "unlist0": vf.get("unlist") or "",
2605
2712
  "see_dots": self.args.see_dots,
2713
+ "dqdel": self.args.qdel,
2606
2714
  "dgrid": "grid" in vf,
2607
2715
  "dgsel": "gsel" in vf,
2608
2716
  "dnsort": "nsort" in vf,
copyparty/bos/bos.py CHANGED
@@ -22,14 +22,26 @@ def listdir(p = ".") :
22
22
 
23
23
 
24
24
  def makedirs(name , mode = 0o755, exist_ok = True) :
25
+ # os.makedirs does 777 for all but leaf; this does mode on all
26
+ todo = []
25
27
  bname = fsenc(name)
26
- try:
27
- os.makedirs(bname, mode)
28
- return True
29
- except:
30
- if not exist_ok or not os.path.isdir(bname):
31
- raise
28
+ while bname:
29
+ if os.path.isdir(bname):
30
+ break
31
+ todo.append(bname)
32
+ bname = os.path.dirname(bname)
33
+ if not todo:
34
+ if not exist_ok:
35
+ os.mkdir(bname) # to throw
32
36
  return False
37
+ for zb in todo[::-1]:
38
+ try:
39
+ os.mkdir(zb, mode)
40
+ except:
41
+ if os.path.isdir(zb):
42
+ continue
43
+ raise
44
+ return True
33
45
 
34
46
 
35
47
  def mkdir(p , mode = 0o755) :
copyparty/cfg.py CHANGED
@@ -78,6 +78,8 @@ def vf_vmap() :
78
78
  }
79
79
  for k in (
80
80
  "bup_ck",
81
+ "chmod_d",
82
+ "chmod_f",
81
83
  "dbd",
82
84
  "forget_ip",
83
85
  "hsortn",
@@ -169,6 +171,8 @@ flagcats = {
169
171
  "safededup": "verify on-disk data before using it for dedup",
170
172
  "noclone": "take dupe data from clients, even if available on HDD",
171
173
  "nodupe": "rejects existing files (instead of linking/cloning them)",
174
+ "chmod_d=755": "unix-permission for new dirs/folders",
175
+ "chmod_f=644": "unix-permission for new files",
172
176
  "sparse": "force use of sparse files, mainly for s3-backed storage",
173
177
  "nosparse": "deny use of sparse files, mainly for slow storage",
174
178
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
@@ -218,6 +222,7 @@ flagcats = {
218
222
  "d2d": "disables all database stuff, overrides -e2*",
219
223
  "hist=/tmp/cdb": "puts thumbnails and indexes at that location",
220
224
  "dbpath=/tmp/cdb": "puts indexes at that location",
225
+ "landmark=foo": "disable db if file foo doesn't exist",
221
226
  "scan=60": "scan for new files every 60sec, same as --re-maxage",
222
227
  "nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
223
228
  "noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
copyparty/ftpd.py CHANGED
@@ -225,7 +225,7 @@ class FtpFs(AbstractedFS):
225
225
  r = "r" in mode
226
226
  w = "w" in mode or "a" in mode or "+" in mode
227
227
 
228
- ap = self.rv2a(filename, r, w)[0]
228
+ ap, vfs, _ = self.rv2a(filename, r, w)
229
229
  self.validpath(ap)
230
230
  if w:
231
231
  try:
@@ -257,7 +257,11 @@ class FtpFs(AbstractedFS):
257
257
 
258
258
  wunlink(self.log, ap, VF_CAREFUL)
259
259
 
260
- return open(fsenc(ap), mode, self.args.iobuf)
260
+ ret = open(fsenc(ap), mode, self.args.iobuf)
261
+ if w and "chmod_f" in vfs.flags:
262
+ os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
263
+
264
+ return ret
261
265
 
262
266
  def chdir(self, path ) :
263
267
  nwd = join(self.cwd, path)
@@ -288,8 +292,9 @@ class FtpFs(AbstractedFS):
288
292
  ) = avfs.can_access("", self.h.uname)
289
293
 
290
294
  def mkdir(self, path ) :
291
- ap = self.rv2a(path, w=True)[0]
292
- bos.makedirs(ap) # filezilla expects this
295
+ ap, vfs, _ = self.rv2a(path, w=True)
296
+ chmod = vfs.flags["chmod_d"]
297
+ bos.makedirs(ap, chmod) # filezilla expects this
293
298
 
294
299
  def listdir(self, path ) :
295
300
  vpath = join(self.cwd, path)
copyparty/httpcli.py CHANGED
@@ -45,6 +45,7 @@ from .util import (
45
45
  APPLESAN_RE,
46
46
  BITNESS,
47
47
  DAV_ALLPROPS,
48
+ E_SCK_WR,
48
49
  FN_EMB,
49
50
  HAVE_SQLITE3,
50
51
  HTTPCODE,
@@ -1369,12 +1370,13 @@ class HttpCli(object):
1369
1370
  title = self.uparam.get("title") or self.vpath.split("/")[-1]
1370
1371
  etitle = html_escape(title, True, True)
1371
1372
 
1372
- baseurl = "%s://%s%s" % (
1373
+ baseurl = "%s://%s/" % (
1373
1374
  "https" if self.is_https else "http",
1374
1375
  self.host,
1375
- self.args.SRS,
1376
1376
  )
1377
- feed = "%s%s" % (baseurl, self.req[1:])
1377
+ feed = baseurl + self.req[1:]
1378
+ if self.is_vproxied:
1379
+ baseurl += self.args.RS
1378
1380
  efeed = html_escape(feed, True, True)
1379
1381
  edirlink = efeed.split("?")[0] + q_pw
1380
1382
 
@@ -1387,7 +1389,7 @@ class HttpCli(object):
1387
1389
  \t\t<title>%s</title>
1388
1390
  \t\t<description></description>
1389
1391
  \t\t<link>%s</link>
1390
- \t\t<generator>copyparty-1</generator>
1392
+ \t\t<generator>copyparty-2</generator>
1391
1393
  """
1392
1394
  % (efeed, etitle, edirlink)
1393
1395
  ]
@@ -2058,7 +2060,7 @@ class HttpCli(object):
2058
2060
  fdir, fn = os.path.split(fdir)
2059
2061
  rem, _ = vsplit(rem)
2060
2062
 
2061
- bos.makedirs(fdir)
2063
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2062
2064
 
2063
2065
  open_ka = {"fun": open}
2064
2066
  open_a = ["wb", self.args.iobuf]
@@ -2116,6 +2118,8 @@ class HttpCli(object):
2116
2118
  fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
2117
2119
 
2118
2120
  params = {"suffix": suffix, "fdir": fdir}
2121
+ if "chmod_f" in vfs.flags:
2122
+ params["chmod"] = vfs.flags["chmod_f"]
2119
2123
  if self.args.nw:
2120
2124
  params = {}
2121
2125
  fn = os.devnull
@@ -2163,7 +2167,7 @@ class HttpCli(object):
2163
2167
  if self.args.nw:
2164
2168
  fn = os.devnull
2165
2169
  else:
2166
- bos.makedirs(fdir)
2170
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2167
2171
  path = os.path.join(fdir, fn)
2168
2172
  if not nameless:
2169
2173
  self.vpath = vjoin(self.vpath, fn)
@@ -2295,7 +2299,7 @@ class HttpCli(object):
2295
2299
  if self.args.hook_v:
2296
2300
  log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
2297
2301
  fdir, self.vpath, fn, (vfs, rem) = x
2298
- bos.makedirs(fdir)
2302
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2299
2303
  path2 = os.path.join(fdir, fn)
2300
2304
  atomic_move(self.log, path, path2, vfs.flags)
2301
2305
  path = path2
@@ -2580,7 +2584,7 @@ class HttpCli(object):
2580
2584
  dst = vfs.canonical(rem)
2581
2585
  try:
2582
2586
  if not bos.path.isdir(dst):
2583
- bos.makedirs(dst)
2587
+ bos.makedirs(dst, vfs.flags["chmod_d"])
2584
2588
  except OSError as ex:
2585
2589
  self.log("makedirs failed %r" % (dst,))
2586
2590
  if not bos.path.isdir(dst):
@@ -3011,7 +3015,7 @@ class HttpCli(object):
3011
3015
  raise Pebkac(405, 'folder "/%s" already exists' % (vpath,))
3012
3016
 
3013
3017
  try:
3014
- bos.makedirs(fn)
3018
+ bos.makedirs(fn, vfs.flags["chmod_d"])
3015
3019
  except OSError as ex:
3016
3020
  if ex.errno == errno.EACCES:
3017
3021
  raise Pebkac(500, "the server OS denied write-access")
@@ -3052,6 +3056,8 @@ class HttpCli(object):
3052
3056
 
3053
3057
  with open(fsenc(fn), "wb") as f:
3054
3058
  f.write(b"`GRUNNUR`\n")
3059
+ if "chmod_f" in vfs.flags:
3060
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3055
3061
 
3056
3062
  vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
3057
3063
  self.redirect(vpath, "?edit")
@@ -3125,7 +3131,7 @@ class HttpCli(object):
3125
3131
  )
3126
3132
  upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
3127
3133
  if not nullwrite:
3128
- bos.makedirs(fdir_base)
3134
+ bos.makedirs(fdir_base, vfs.flags["chmod_d"])
3129
3135
 
3130
3136
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
3131
3137
  zs = self.uparam.get("want") or self.headers.get("accept") or ""
@@ -3220,8 +3226,11 @@ class HttpCli(object):
3220
3226
  else:
3221
3227
  open_args["fdir"] = fdir
3222
3228
 
3229
+ if "chmod_f" in vfs.flags:
3230
+ open_args["chmod"] = vfs.flags["chmod_f"]
3231
+
3223
3232
  if p_file and not nullwrite:
3224
- bos.makedirs(fdir)
3233
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
3225
3234
 
3226
3235
  # reserve destination filename
3227
3236
  f, fname = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
@@ -3325,7 +3334,7 @@ class HttpCli(object):
3325
3334
  if nullwrite:
3326
3335
  fdir = ap2 = ""
3327
3336
  else:
3328
- bos.makedirs(fdir)
3337
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
3329
3338
  atomic_move(self.log, abspath, ap2, vfs.flags)
3330
3339
  abspath = ap2
3331
3340
  sz = bos.path.getsize(abspath)
@@ -3446,6 +3455,8 @@ class HttpCli(object):
3446
3455
  ft = "{}:{}".format(self.ip, self.addr[1])
3447
3456
  ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
3448
3457
  f.write(ft.encode("utf-8"))
3458
+ if "chmod_f" in vfs.flags:
3459
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3449
3460
  except Exception as ex:
3450
3461
  suf = "\nfailed to write the upload report: {}".format(ex)
3451
3462
 
@@ -3495,7 +3506,7 @@ class HttpCli(object):
3495
3506
  lim = vfs.get_dbv(rem)[0].lim
3496
3507
  if lim:
3497
3508
  fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker)
3498
- bos.makedirs(fp)
3509
+ bos.makedirs(fp, vfs.flags["chmod_d"])
3499
3510
 
3500
3511
  fp = os.path.join(fp, fn)
3501
3512
  rem = "{}/{}".format(rp, fn).strip("/")
@@ -3563,13 +3574,15 @@ class HttpCli(object):
3563
3574
  zs = ub64enc(zb).decode("ascii")[:24].lower()
3564
3575
  dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
3565
3576
  self.log("moving old version to %s/%s" % (dp, mfile2))
3566
- if bos.makedirs(dp):
3577
+ if bos.makedirs(dp, vfs.flags["chmod_d"]):
3567
3578
  with open(os.path.join(dp, "dir.txt"), "wb") as f:
3568
3579
  f.write(afsenc(vrd))
3580
+ if "chmod_f" in vfs.flags:
3581
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3569
3582
  elif hist_cfg == "s":
3570
3583
  dp = os.path.join(mdir, ".hist")
3571
3584
  try:
3572
- bos.mkdir(dp)
3585
+ bos.mkdir(dp, vfs.flags["chmod_d"])
3573
3586
  hidedir(dp)
3574
3587
  except:
3575
3588
  pass
@@ -3607,6 +3620,8 @@ class HttpCli(object):
3607
3620
  wunlink(self.log, fp, vfs.flags)
3608
3621
 
3609
3622
  with open(fsenc(fp), "wb", self.args.iobuf) as f:
3623
+ if "chmod_f" in vfs.flags:
3624
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3610
3625
  sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp)
3611
3626
 
3612
3627
  if lim:
@@ -4342,7 +4357,7 @@ class HttpCli(object):
4342
4357
  self.log("file deleted; disconnecting")
4343
4358
  break
4344
4359
  except IOError as ex:
4345
- if ex.errno not in (errno.EPIPE, errno.ESHUTDOWN, errno.EBADFD):
4360
+ if ex.errno not in E_SCK_WR:
4346
4361
  raise
4347
4362
  finally:
4348
4363
  if f:
@@ -5534,7 +5549,7 @@ class HttpCli(object):
5534
5549
  db.commit()
5535
5550
  db.close()
5536
5551
 
5537
- self.conn.hsrv.broker.ask("reload", False, False).get()
5552
+ self.conn.hsrv.broker.ask("reload", False, True).get()
5538
5553
 
5539
5554
  self.redirect("", "?idp")
5540
5555
  return True
@@ -5618,7 +5633,7 @@ class HttpCli(object):
5618
5633
 
5619
5634
  cur.connection.commit()
5620
5635
  if reload:
5621
- self.conn.hsrv.broker.ask("reload", False, False).get()
5636
+ self.conn.hsrv.broker.ask("reload", False, True).get()
5622
5637
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
5623
5638
 
5624
5639
  self.redirect("", "?shares")
@@ -5710,7 +5725,7 @@ class HttpCli(object):
5710
5725
  cur.execute(q, (skey, fn))
5711
5726
 
5712
5727
  cur.connection.commit()
5713
- self.conn.hsrv.broker.ask("reload", False, False).get()
5728
+ self.conn.hsrv.broker.ask("reload", False, True).get()
5714
5729
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
5715
5730
 
5716
5731
  fn = quotep(fns[0]) if len(fns) == 1 else ""
copyparty/smbd.py CHANGED
@@ -317,7 +317,7 @@ class SMB(object):
317
317
 
318
318
  self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
319
319
  try:
320
- bos.makedirs(ap2)
320
+ bos.makedirs(ap2, vfs2.flags["chmod_d"])
321
321
  except:
322
322
  pass
323
323
 
@@ -331,7 +331,7 @@ class SMB(object):
331
331
  t = "blocked mkdir (no-write-acc %s): /%s @%s"
332
332
  yeet(t % (vfs.axs.uwrite, vpath, uname))
333
333
 
334
- return bos.mkdir(ap)
334
+ return bos.mkdir(ap, vfs.flags["chmod_d"])
335
335
 
336
336
  def _stat(self, vpath , *a , **ka ) :
337
337
  try:
copyparty/svchub.py CHANGED
@@ -21,6 +21,7 @@ from datetime import datetime
21
21
 
22
22
  from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
23
23
  from .authsrv import BAD_CFG, AuthSrv
24
+ from .bos import bos
24
25
  from .cert import ensure_cert
25
26
  from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
26
27
  from .pwhash import HAVE_ARGON2
@@ -1108,7 +1109,7 @@ class SvcHub(object):
1108
1109
 
1109
1110
  fn = sel_fn
1110
1111
  try:
1111
- os.makedirs(os.path.dirname(fn))
1112
+ bos.makedirs(os.path.dirname(fn))
1112
1113
  except:
1113
1114
  pass
1114
1115
 
@@ -1125,6 +1126,9 @@ class SvcHub(object):
1125
1126
 
1126
1127
  lh = codecs.open(fn, "w", encoding="utf-8", errors="replace")
1127
1128
 
1129
+ if getattr(self.args, "free_umask", False):
1130
+ os.fchmod(lh.fileno(), 0o644)
1131
+
1128
1132
  argv = [pybin] + self.argv
1129
1133
  if hasattr(shlex, "quote"):
1130
1134
  argv = [shlex.quote(x) for x in argv]
copyparty/tcpsrv.py CHANGED
@@ -279,7 +279,7 @@ class TcpSrv(object):
279
279
  except:
280
280
  pass # will create another ipv4 socket instead
281
281
 
282
- if not ANYWIN and self.args.freebind:
282
+ if getattr(self.args, "freebind", False):
283
283
  srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
284
284
 
285
285
  try:
copyparty/tftpd.py CHANGED
@@ -384,14 +384,18 @@ class Tftpd(object):
384
384
  if not a:
385
385
  a = (self.args.iobuf,)
386
386
 
387
- return open(ap, mode, *a, **ka)
387
+ ret = open(ap, mode, *a, **ka)
388
+ if wr and "chmod_f" in vfs.flags:
389
+ os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
390
+
391
+ return ret
388
392
 
389
393
  def _mkdir(self, vpath , *a) :
390
394
  vfs, _, ap = self._v2a("mkdir", vpath, [False, True])
391
395
  if "*" not in vfs.axs.uwrite:
392
396
  yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
393
397
 
394
- return bos.mkdir(ap)
398
+ return bos.mkdir(ap, vfs.flags["chmod_d"])
395
399
 
396
400
  def _unlink(self, vpath ) :
397
401
  # return bos.unlink(self._v2a("stat", vpath, *a)[1])
copyparty/th_srv.py CHANGED
@@ -266,7 +266,8 @@ class ThumbSrv(object):
266
266
  self.log("joined waiting room for %r" % (tpath,))
267
267
  except:
268
268
  thdir = os.path.dirname(tpath)
269
- bos.makedirs(os.path.join(thdir, "w"))
269
+ chmod = 0o700 if self.args.free_umask else 0o755
270
+ bos.makedirs(os.path.join(thdir, "w"), chmod)
270
271
 
271
272
  inf_path = os.path.join(thdir, "dir.txt")
272
273
  if not bos.path.exists(inf_path):
@@ -281,7 +282,7 @@ class ThumbSrv(object):
281
282
  vn = next((x for x in allvols if x.realpath == ptop), None)
282
283
  if not vn:
283
284
  self.log("ptop %r not in %s" % (ptop, allvols), 3)
284
- vn = self.asrv.vfs.all_aps[0][1]
285
+ vn = self.asrv.vfs.all_aps[0][1][0]
285
286
 
286
287
  self.q.put((abspath, tpath, fmt, vn))
287
288
  self.log("conv %r :%s \033[0m%r" % (tpath, fmt, abspath), 6)
copyparty/up2k.py CHANGED
@@ -910,7 +910,8 @@ class Up2k(object):
910
910
  # only need to protect register_vpath but all in one go feels right
911
911
  for vol in vols:
912
912
  try:
913
- bos.makedirs(vol.realpath) # gonna happen at snap anyways
913
+ # mkdir gonna happen at snap anyways;
914
+ bos.makedirs(vol.realpath, vol.flags["chmod_d"])
914
915
  dir_is_empty(self.log_func, not self.args.no_scandir, vol.realpath)
915
916
  except Exception as ex:
916
917
  self.volstate[vol.vpath] = "OFFLINE (cannot access folder)"
@@ -1134,6 +1135,20 @@ class Up2k(object):
1134
1135
  del fl[k1]
1135
1136
  else:
1136
1137
  fl[k1] = ",".join(x for x in fl[k1])
1138
+
1139
+ if fl["chmod_d"] == int(self.args.chmod_d, 8):
1140
+ fl.pop("chmod_d")
1141
+ try:
1142
+ if fl["chmod_f"] == int(self.args.chmod_f or "-1", 8):
1143
+ fl.pop("chmod_f")
1144
+ except:
1145
+ pass
1146
+ for k in ("chmod_f", "chmod_d"):
1147
+ try:
1148
+ fl[k] = "%o" % (fl[k])
1149
+ except:
1150
+ pass
1151
+
1137
1152
  a = [
1138
1153
  (ft if v is True else ff if v is False else fv).format(k, str(v))
1139
1154
  for k, v in fl.items()
@@ -1356,6 +1371,10 @@ class Up2k(object):
1356
1371
  t = "volume /%s at [%s] is empty; will not be indexed as this could be due to an offline filesystem"
1357
1372
  self.log(t % (vol.vpath, rtop), 6)
1358
1373
  return True, False
1374
+ if not vol.check_landmarks():
1375
+ t = "volume /%s at [%s] will not be indexed due to bad landmarks"
1376
+ self.log(t % (vol.vpath, rtop), 6)
1377
+ return True, False
1359
1378
 
1360
1379
  n_add, _, _ = self._build_dir(
1361
1380
  db,
@@ -3274,7 +3293,7 @@ class Up2k(object):
3274
3293
  reg,
3275
3294
  "up2k._get_volsize",
3276
3295
  )
3277
- bos.makedirs(ap2)
3296
+ bos.makedirs(ap2, vfs.flags["chmod_d"])
3278
3297
  vfs.lim.nup(cj["addr"])
3279
3298
  vfs.lim.bup(cj["addr"], cj["size"])
3280
3299
 
@@ -3381,11 +3400,11 @@ class Up2k(object):
3381
3400
  self.log(t % (mts - mtc, mts, mtc, fp))
3382
3401
  ow = False
3383
3402
 
3403
+ ptop = job["ptop"]
3404
+ vf = self.flags.get(ptop) or {}
3384
3405
  if ow:
3385
3406
  self.log("replacing existing file at %r" % (fp,))
3386
3407
  cur = None
3387
- ptop = job["ptop"]
3388
- vf = self.flags.get(ptop) or {}
3389
3408
  st = bos.stat(fp)
3390
3409
  try:
3391
3410
  vrel = vjoin(job["prel"], fname)
@@ -3405,8 +3424,13 @@ class Up2k(object):
3405
3424
  else:
3406
3425
  dip = self.hub.iphash.s(ip)
3407
3426
 
3408
- suffix = "-%.6f-%s" % (ts, dip)
3409
- f, ret = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
3427
+ f, ret = ren_open(
3428
+ fname,
3429
+ "wb",
3430
+ fdir=fdir,
3431
+ suffix="-%.6f-%s" % (ts, dip),
3432
+ chmod=vf.get("chmod_f", -1),
3433
+ )
3410
3434
  f.close()
3411
3435
  return ret
3412
3436
 
@@ -4256,7 +4280,7 @@ class Up2k(object):
4256
4280
  self.log(t, 1)
4257
4281
  raise Pebkac(405, t)
4258
4282
 
4259
- bos.makedirs(os.path.dirname(dabs))
4283
+ bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4260
4284
 
4261
4285
  c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
4262
4286
  svn_dbv.realpath, srem_dbv
@@ -4431,7 +4455,7 @@ class Up2k(object):
4431
4455
  vp = vjoin(dvp, rem)
4432
4456
  try:
4433
4457
  dvn, drem = self.vfs.get(vp, uname, False, True)
4434
- bos.mkdir(dvn.canonical(drem))
4458
+ bos.mkdir(dvn.canonical(drem), dvn.flags["chmod_d"])
4435
4459
  except:
4436
4460
  pass
4437
4461
 
@@ -4501,7 +4525,7 @@ class Up2k(object):
4501
4525
 
4502
4526
  is_xvol = svn.realpath != dvn.realpath
4503
4527
 
4504
- bos.makedirs(os.path.dirname(dabs))
4528
+ bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4505
4529
 
4506
4530
  if is_dirlink:
4507
4531
  dlabs = absreal(sabs)
@@ -5006,8 +5030,13 @@ class Up2k(object):
5006
5030
  else:
5007
5031
  dip = self.hub.iphash.s(job["addr"])
5008
5032
 
5009
- suffix = "-%.6f-%s" % (job["t0"], dip)
5010
- f, job["tnam"] = ren_open(tnam, "wb", fdir=pdir, suffix=suffix)
5033
+ f, job["tnam"] = ren_open(
5034
+ tnam,
5035
+ "wb",
5036
+ fdir=pdir,
5037
+ suffix="-%.6f-%s" % (job["t0"], dip),
5038
+ chmod=vf.get("chmod_f", -1),
5039
+ )
5011
5040
  try:
5012
5041
  abspath = djoin(pdir, job["tnam"])
5013
5042
  sprs = job["sprs"]
copyparty/util.py CHANGED
@@ -105,6 +105,7 @@ def _ens(want ) :
105
105
  # WSAENOTSOCK - no longer a socket
106
106
  # EUNATCH - can't assign requested address (wifi down)
107
107
  E_SCK = _ens("ENOTCONN EUNATCH EBADF WSAENOTSOCK WSAECONNRESET")
108
+ E_SCK_WR = _ens("EPIPE ESHUTDOWN EBADFD")
108
109
  E_ADDR_NOT_AVAIL = _ens("EADDRNOTAVAIL WSAEADDRNOTAVAIL")
109
110
  E_ADDR_IN_USE = _ens("EADDRINUSE WSAEADDRINUSE")
110
111
  E_ACCESS = _ens("EACCES WSAEACCES")
@@ -1502,6 +1503,7 @@ def ren_open(fname , *args , **kwargs ) :
1502
1503
  fun = kwargs.pop("fun", open)
1503
1504
  fdir = kwargs.pop("fdir", None)
1504
1505
  suffix = kwargs.pop("suffix", None)
1506
+ chmod = kwargs.pop("chmod", -1)
1505
1507
 
1506
1508
  if fname == os.devnull:
1507
1509
  return fun(fname, *args, **kwargs), fname
@@ -1544,6 +1546,11 @@ def ren_open(fname , *args , **kwargs ) :
1544
1546
  fp2 = os.path.join(fdir, fp2)
1545
1547
  with open(fsenc(fp2), "wb") as f2:
1546
1548
  f2.write(orig_name.encode("utf-8"))
1549
+ if chmod >= 0:
1550
+ os.fchmod(f2.fileno(), chmod)
1551
+
1552
+ if chmod >= 0:
1553
+ os.fchmod(f.fileno(), chmod)
1547
1554
 
1548
1555
  return f, fname
1549
1556
 
@@ -1882,7 +1889,7 @@ def rand_name(fdir , fn , rnd ) :
1882
1889
  return fn
1883
1890
 
1884
1891
 
1885
- def gen_filekey(alg , salt , fspath , fsize , inode ) :
1892
+ def _gen_filekey(alg , salt , fspath , fsize , inode ) :
1886
1893
  if alg == 1:
1887
1894
  zs = "%s %s %s %s" % (salt, fspath, fsize, inode)
1888
1895
  else:
@@ -1892,6 +1899,13 @@ def gen_filekey(alg , salt , fspath , fsize , inode ) :
1892
1899
  return ub64enc(hashlib.sha512(zb).digest()).decode("ascii")
1893
1900
 
1894
1901
 
1902
+ def _gen_filekey_w(alg , salt , fspath , fsize , inode ) :
1903
+ return _gen_filekey(alg, salt, fspath.replace("/", "\\"), fsize, inode)
1904
+
1905
+
1906
+ gen_filekey = _gen_filekey_w if ANYWIN else _gen_filekey
1907
+
1908
+
1895
1909
  def gen_filekey_dbg(
1896
1910
  alg ,
1897
1911
  salt ,
@@ -2314,11 +2328,11 @@ def pathmod(
2314
2328
 
2315
2329
  # try to map abspath to vpath
2316
2330
  np = np.replace("/", os.sep)
2317
- for vn_ap, vn in vfs.all_aps:
2331
+ for vn_ap, vns in vfs.all_aps:
2318
2332
  if not np.startswith(vn_ap):
2319
2333
  continue
2320
2334
  zs = np[len(vn_ap) :].replace(os.sep, "/")
2321
- nvp = vjoin(vn.vpath, zs)
2335
+ nvp = vjoin(vns[0].vpath, zs)
2322
2336
  break
2323
2337
 
2324
2338
  if nvp == "\n":
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.2
3
+ Version: 1.18.4
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -1663,7 +1663,7 @@ config file example:
1663
1663
  w: * # anyone can upload here
1664
1664
  rw: ed # only user "ed" can read-write
1665
1665
  flags:
1666
- e2ds: # filesystem indexing is required for many of these:
1666
+ e2ds # filesystem indexing is required for many of these:
1667
1667
  sz: 1k-3m # accept upload only if filesize in this range
1668
1668
  df: 4g # free disk space cannot go lower than this
1669
1669
  vmaxb: 1g # volume can never exceed 1 GiB
@@ -1720,6 +1720,8 @@ this can instead be kept in a single place using the `--hist` argument, or the `
1720
1720
 
1721
1721
  by default, the per-volume `up2k.db` sqlite3-database for `-e2d` and `-e2t` is stored next to the thumbnails according to the `--hist` option, but the global-option `--dbpath` and/or volflag `dbpath` can be used to put the database somewhere else
1722
1722
 
1723
+ if your storage backend is unreliable (NFS or bad HDDs), you can specify one or more "landmarks" to look for before doing anything database-related. A landmark is a file which is always expected to exist inside the volume. This avoids spurious filesystem rescans in the event of an outage. One line per landmark (see example below)
1724
+
1723
1725
  note:
1724
1726
  * putting the hist-folders on an SSD is strongly recommended for performance
1725
1727
  * markdown edits are always stored in a local `.hist` subdirectory
@@ -1737,6 +1739,8 @@ config file example:
1737
1739
  flags:
1738
1740
  hist: - # restore the default (/mnt/nas/pics/.hist/)
1739
1741
  hist: /mnt/nas/cache/pics/ # can be absolute path
1742
+ landmark: me.jpg # /mnt/nas/pics/me.jpg must be readable to enable db
1743
+ landmark: info/a.txt^=ok # and this textfile must start with "ok"
1740
1744
  ```
1741
1745
 
1742
1746
 
@@ -2422,8 +2426,10 @@ TLDR: yes
2422
2426
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2423
2427
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2424
2428
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2429
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2425
2430
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2426
2431
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2432
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2427
2433
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2428
2434
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2429
2435
  | video player | - | yep | yep | yep | yep | yep | yep | yep |
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=4aJw_Mt3eSNMV8sJ95Nh4ris-tBUYhCOV094Rnxa5Xo,2651
2
- copyparty/__main__.py,sha256=7a7hb4Zo2YFsidhN9Pd-14WPmljKU7sjIG9rI049_TI,123870
3
- copyparty/__version__.py,sha256=ytENbEsCnReBVxna8oK4WGF12_-rjNACWOmKX-nt_dQ,248
4
- copyparty/authsrv.py,sha256=o1B_VuK1iXFWUQ6unzQZVdAOba3pBnLvHyAImqpr01s,116647
2
+ copyparty/__main__.py,sha256=rfSaVAHy9NpF6U-dWu4NS6zab96v7DLnG95M7WRXyxo,126024
3
+ copyparty/__version__.py,sha256=VYBE-faUq6KuT0awfdhYnaqRvlnX2Tni0a63zOMGx_A,249
4
+ copyparty/authsrv.py,sha256=PDX6-ob-vqpKE8XkMCEph662EE4Mp8IKbtIEZUKk5IM,120514
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
8
8
  copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
9
9
  copyparty/cert.py,sha256=pSSeVYticrDsnsrdRtfpUQN-8WRObsqrYtSRroXmgxo,7992
10
- copyparty/cfg.py,sha256=XFHzYS2p5-1VBnzyVWVVZMpvJ6JBTirs099ny3BOc14,15202
10
+ copyparty/cfg.py,sha256=O2jhYbt7WV9X80_Av1f7r4u1JVi7U5Z_cLgSGhpipLg,15423
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
13
- copyparty/ftpd.py,sha256=G7PApVIFeSzRo4-D-9uRb8NxYgz6nFwTEbrOk1ErYCU,17969
14
- copyparty/httpcli.py,sha256=yIiqaQRperqJHlfN9EiUQB_hfMOwnutjVn23LnptKIA,229664
13
+ copyparty/ftpd.py,sha256=xDDWixo5O2HT8wlexXQ0QRazU_fYERIJ4yF0mtu9wD0,18141
14
+ copyparty/httpcli.py,sha256=ZNwaI4DVM6ljDz5ALRVuk07qG19i66_CLm9h6eARTSA,230544
15
15
  copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
16
  copyparty/httpsrv.py,sha256=x6dl6ZjpwYREbm-eJZYnwdkhDeCA58hw_iwUMCD1Wz8,18819
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
@@ -20,21 +20,21 @@ copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
20
  copyparty/mtag.py,sha256=7lINRFc1Vrc-ebP2hU3dBHH3BRFOnEmEZ0jzlpYb8Jg,19910
21
21
  copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
22
22
  copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
23
- copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
23
+ copyparty/smbd.py,sha256=jeKGkVLG9-Q0zPNDZdIwb_O59388rvGzG2cQQ2qPXpk,14659
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
- copyparty/svchub.py,sha256=YbYJd5X0pXAyC-C2nt1rVwnwd56b8VEU1zvGkijgNeU,48921
27
+ copyparty/svchub.py,sha256=M7UnnqNUtVz5DQRYMHcmmyvDDtPzXKAOrW-XgJWfujc,49038
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
- copyparty/tcpsrv.py,sha256=F5K4Qr4eBLfhdLT_39yDf6ftrhWuGTrd6DSqqp_6e-Q,20480
30
- copyparty/tftpd.py,sha256=tbnxUsilwyusrAUCVVjJUZnR9TIHDkE-99WLsUxAIGA,14029
29
+ copyparty/tcpsrv.py,sha256=BCOqlT_mRu1ibHJpPzvf9c4h83AnIMEfd8nBBednCCg,20484
30
+ copyparty/tftpd.py,sha256=sNBMqazIB37t3jwhv_F1tr0lHFsiRX-nCsICM8nbkvc,14170
31
31
  copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=6JXHthaZtDreWHmyRxfxN_EyNC2aQOQS5wUFYxxX-3Y,32669
32
+ copyparty/th_srv.py,sha256=INLaV1_NH1VKtBx9XaqyJbZNAj2ZhfcQwz0_5Y5fyiY,32744
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
- copyparty/up2k.py,sha256=nnR_ZKaopSNBuAjSN3Q5G_UVe6GmYD1NkxJt5QQf02o,178079
35
- copyparty/util.py,sha256=S-dEme_1rKf3qYgxWWahEUQUzgq_kpoO_nPbuKCwLsY,103948
34
+ copyparty/up2k.py,sha256=S-SPTzLqxFvgSkFsWHqJwgUeT7QguLZgdZMQIh5eWiU,178957
35
+ copyparty/util.py,sha256=p4Z2JHjIKWo-x5RS1kQl5qBG5T_E1FUV2pRfukAdyzA,104391
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
37
+ copyparty/bos/bos.py,sha256=ZwpTla_mFpoN_BbGn9h0dEn7wpZ607QJ7uYyxWJMcmw,1953
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
39
39
  copyparty/res/COPYING.txt,sha256=1LnBxkwJuC8mRBxuoMF2UIcpCjcteIzFHotSUE1Xte0,9776
40
40
  copyparty/res/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,10 +54,10 @@ copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABw
54
54
  copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUuTOrU,2626
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
- copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
- copyparty/web/browser.css.gz,sha256=QRTGDf5GmNGCcly_pvxf0sKRCU9T9sQdhJB39_EoYkA,11789
57
+ copyparty/web/baguettebox.js.gz,sha256=MxRofvhXjmUN7RtXtC17_9AlROVNUT-66WwJ_pHLY9c,8258
58
+ copyparty/web/browser.css.gz,sha256=29D3F4uB-VMd6uJo-SxWAwLfn08jcETYZm0SOJFJLAE,11847
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=1wAt4IULmQM0Mwn0NkbDgypXPumY28C97ViB-PWgAsI,96508
60
+ copyparty/web/browser.js.gz,sha256=EZ58H7mtO6VEjeNxU5ZOlbelSRTuc_-JAmAGd3S11aY,96694
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -110,9 +110,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
110
110
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
111
111
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
112
112
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
113
- copyparty-1.18.2.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
- copyparty-1.18.2.dist-info/METADATA,sha256=H51jbwvMVishSC0aPsVuie6BikMnnxuD1FLfQexP46c,165791
115
- copyparty-1.18.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
- copyparty-1.18.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
- copyparty-1.18.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
- copyparty-1.18.2.dist-info/RECORD,,
113
+ copyparty-1.18.4.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
+ copyparty-1.18.4.dist-info/METADATA,sha256=4D8UUVWS75gzR3fW8ngJNPa_TNcGz8Dmci1CF_OHzq8,166420
115
+ copyparty-1.18.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ copyparty-1.18.4.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
+ copyparty-1.18.4.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
+ copyparty-1.18.4.dist-info/RECORD,,