copyparty 1.18.2__py3-none-any.whl → 1.18.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
@@ -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")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 2)
3
+ VERSION = (1, 18, 3)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 7)
5
+ BUILD_DT = (2025, 7, 21)
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 = {}
@@ -384,7 +387,7 @@ class VFS(object):
384
387
  self.dbpath = self.histpath
385
388
  self.all_vols = {vpath: self} # flattened recursive
386
389
  self.all_nodes = {vpath: self} # also jumpvols/shares
387
- self.all_aps = [(rp, self)]
390
+ self.all_aps = [(rp, [self])]
388
391
  self.all_vps = [(vp, self)]
389
392
  else:
390
393
  self.histpath = self.dbpath = ""
@@ -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)
@@ -915,6 +924,9 @@ class AuthSrv(object):
915
924
 
916
925
  yield prev, True
917
926
 
927
+ def vf0(self):
928
+ return {"d2d": True, "tcolor": self.args.tcolor}
929
+
918
930
  def idp_checkin(
919
931
  self, broker , uname , gname
920
932
  ) :
@@ -1620,13 +1632,12 @@ class AuthSrv(object):
1620
1632
  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
1633
  self.log(t, 1)
1622
1634
  axs = AXS()
1623
- vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1635
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1624
1636
  if not axs.uread:
1625
1637
  self.badcfg1 = True
1626
1638
  elif "" not in mount:
1627
1639
  # 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)
1640
+ vfs = VFS(self.log_func, "", "", "", AXS(), self.vf0())
1630
1641
 
1631
1642
  maxdepth = 0
1632
1643
  for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
@@ -1674,8 +1685,7 @@ class AuthSrv(object):
1674
1685
  shrs = enshare[1:]
1675
1686
  if enshare:
1676
1687
 
1677
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1678
- shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
1688
+ shv = VFS(self.log_func, "", shr, shr, AXS(), self.vf0())
1679
1689
 
1680
1690
  db_path = self.args.shr_db
1681
1691
  db = sqlite3.connect(db_path)
@@ -2057,6 +2067,7 @@ class AuthSrv(object):
2057
2067
 
2058
2068
  all_mte = {}
2059
2069
  errors = False
2070
+ free_umask = False
2060
2071
  for vol in vfs.all_nodes.values():
2061
2072
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2062
2073
  vol.flags["e2ds"] = True
@@ -2113,6 +2124,27 @@ class AuthSrv(object):
2113
2124
  t = 'volume "/%s" has invalid %stry [%s]'
2114
2125
  raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
2115
2126
 
2127
+ for k in ("chmod_d", "chmod_f"):
2128
+ is_d = k == "chmod_d"
2129
+ zs = vol.flags.get(k, "")
2130
+ if not zs and is_d:
2131
+ zs = "755"
2132
+ if not zs:
2133
+ vol.flags.pop(k, None)
2134
+ continue
2135
+ if not re.match("^[0-7]{3}$", zs):
2136
+ t = "config-option '%s' must be a three-digit octal value such as [755] or [644] but the value was [%s]"
2137
+ t = t % (k, zs)
2138
+ self.log(t, 1)
2139
+ raise Exception(t)
2140
+ zi = int(zs, 8)
2141
+ vol.flags[k] = zi
2142
+ if (is_d and zi != 0o755) or not is_d:
2143
+ free_umask = True
2144
+
2145
+ if vol.lim:
2146
+ vol.lim.chmod_d = vol.flags["chmod_d"]
2147
+
2116
2148
  if vol.flags.get("og"):
2117
2149
  self.args.uqe = True
2118
2150
 
@@ -2342,6 +2374,10 @@ class AuthSrv(object):
2342
2374
  if errors:
2343
2375
  sys.exit(1)
2344
2376
 
2377
+ setattr(self.args, "free_umask", free_umask)
2378
+ if free_umask:
2379
+ os.umask(0)
2380
+
2345
2381
  vfs.bubble_flags()
2346
2382
 
2347
2383
  have_e2d = False
@@ -2544,6 +2580,28 @@ class AuthSrv(object):
2544
2580
  shn.shr_src = (s_vfs, s_rem)
2545
2581
  shn.realpath = s_vfs.canonical(s_rem)
2546
2582
 
2583
+ # root.all_aps doesn't include any shares, so make a copy where the
2584
+ # share appears in all abspaths it can provide (for example for chk_ap)
2585
+ ap = shn.realpath
2586
+ if not ap.endswith(os.sep):
2587
+ ap += os.sep
2588
+ shn.shr_all_aps = [(x, y[:]) for x, y in vfs.all_aps]
2589
+ exact = False
2590
+ for ap2, vns in shn.shr_all_aps:
2591
+ if ap == ap2:
2592
+ exact = True
2593
+ if ap2.startswith(ap):
2594
+ try:
2595
+ vp2 = vjoin(s_rem, ap2[len(ap) :])
2596
+ vn2, _ = s_vfs.get(vp2, "*", False, False)
2597
+ if vn2 == s_vfs or vn2.dbv == s_vfs:
2598
+ vns.append(shn)
2599
+ except:
2600
+ pass
2601
+ if not exact:
2602
+ shn.shr_all_aps.append((ap, [shn]))
2603
+ shn.shr_all_aps.sort(key=lambda x: len(x[0]), reverse=True)
2604
+
2547
2605
  if self.args.shr_v:
2548
2606
  t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
2549
2607
  self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
@@ -2558,7 +2616,7 @@ class AuthSrv(object):
2558
2616
  continue # also fine
2559
2617
  for zs in svn.nodes.keys():
2560
2618
  # hide subvolume
2561
- vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), {})
2619
+ vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), self.vf0())
2562
2620
 
2563
2621
  cur2.close()
2564
2622
  cur.close()
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",
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:
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()
@@ -3274,7 +3289,7 @@ class Up2k(object):
3274
3289
  reg,
3275
3290
  "up2k._get_volsize",
3276
3291
  )
3277
- bos.makedirs(ap2)
3292
+ bos.makedirs(ap2, vfs.flags["chmod_d"])
3278
3293
  vfs.lim.nup(cj["addr"])
3279
3294
  vfs.lim.bup(cj["addr"], cj["size"])
3280
3295
 
@@ -3381,11 +3396,11 @@ class Up2k(object):
3381
3396
  self.log(t % (mts - mtc, mts, mtc, fp))
3382
3397
  ow = False
3383
3398
 
3399
+ ptop = job["ptop"]
3400
+ vf = self.flags.get(ptop) or {}
3384
3401
  if ow:
3385
3402
  self.log("replacing existing file at %r" % (fp,))
3386
3403
  cur = None
3387
- ptop = job["ptop"]
3388
- vf = self.flags.get(ptop) or {}
3389
3404
  st = bos.stat(fp)
3390
3405
  try:
3391
3406
  vrel = vjoin(job["prel"], fname)
@@ -3405,8 +3420,13 @@ class Up2k(object):
3405
3420
  else:
3406
3421
  dip = self.hub.iphash.s(ip)
3407
3422
 
3408
- suffix = "-%.6f-%s" % (ts, dip)
3409
- f, ret = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
3423
+ f, ret = ren_open(
3424
+ fname,
3425
+ "wb",
3426
+ fdir=fdir,
3427
+ suffix="-%.6f-%s" % (ts, dip),
3428
+ chmod=vf.get("chmod_f", -1),
3429
+ )
3410
3430
  f.close()
3411
3431
  return ret
3412
3432
 
@@ -4256,7 +4276,7 @@ class Up2k(object):
4256
4276
  self.log(t, 1)
4257
4277
  raise Pebkac(405, t)
4258
4278
 
4259
- bos.makedirs(os.path.dirname(dabs))
4279
+ bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4260
4280
 
4261
4281
  c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
4262
4282
  svn_dbv.realpath, srem_dbv
@@ -4431,7 +4451,7 @@ class Up2k(object):
4431
4451
  vp = vjoin(dvp, rem)
4432
4452
  try:
4433
4453
  dvn, drem = self.vfs.get(vp, uname, False, True)
4434
- bos.mkdir(dvn.canonical(drem))
4454
+ bos.mkdir(dvn.canonical(drem), dvn.flags["chmod_d"])
4435
4455
  except:
4436
4456
  pass
4437
4457
 
@@ -4501,7 +4521,7 @@ class Up2k(object):
4501
4521
 
4502
4522
  is_xvol = svn.realpath != dvn.realpath
4503
4523
 
4504
- bos.makedirs(os.path.dirname(dabs))
4524
+ bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4505
4525
 
4506
4526
  if is_dirlink:
4507
4527
  dlabs = absreal(sabs)
@@ -5006,8 +5026,13 @@ class Up2k(object):
5006
5026
  else:
5007
5027
  dip = self.hub.iphash.s(job["addr"])
5008
5028
 
5009
- suffix = "-%.6f-%s" % (job["t0"], dip)
5010
- f, job["tnam"] = ren_open(tnam, "wb", fdir=pdir, suffix=suffix)
5029
+ f, job["tnam"] = ren_open(
5030
+ tnam,
5031
+ "wb",
5032
+ fdir=pdir,
5033
+ suffix="-%.6f-%s" % (job["t0"], dip),
5034
+ chmod=vf.get("chmod_f", -1),
5035
+ )
5011
5036
  try:
5012
5037
  abspath = djoin(pdir, job["tnam"])
5013
5038
  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
 
@@ -2314,11 +2321,11 @@ def pathmod(
2314
2321
 
2315
2322
  # try to map abspath to vpath
2316
2323
  np = np.replace("/", os.sep)
2317
- for vn_ap, vn in vfs.all_aps:
2324
+ for vn_ap, vns in vfs.all_aps:
2318
2325
  if not np.startswith(vn_ap):
2319
2326
  continue
2320
2327
  zs = np[len(vn_ap) :].replace(os.sep, "/")
2321
- nvp = vjoin(vn.vpath, zs)
2328
+ nvp = vjoin(vns[0].vpath, zs)
2322
2329
  break
2323
2330
 
2324
2331
  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.3
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
@@ -2422,8 +2422,10 @@ TLDR: yes
2422
2422
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2423
2423
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2424
2424
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2425
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2425
2426
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2426
2427
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2428
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2427
2429
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2428
2430
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2429
2431
  | 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=xfjyeCayxt3syWT1Iw6DDitRuQkrUcJvFUaEoWcIfMU,125889
3
+ copyparty/__version__.py,sha256=D7P8wrGVX6EhXwfZYGNDWFK3D95DiMYHg7ohrpDSGMM,249
4
+ copyparty/authsrv.py,sha256=TF6T254t2ZdcB_1nAq5xXQBjG5SKUtzIeN3RBhmzCtc,118943
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=3rMq1XSGnzu5-_YU4DsDc9G2hMOv6VLPveF2mC3_CU8,15359
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=lB7CvrqyWMkXAV39oJtPof5PzDRCg7nNh7cwOoVfgO4,230547
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=KINADhvYxtCG6NEC2qytdBCsjqvOiaVR5tc6nmvlaho,178731
35
+ copyparty/util.py,sha256=9juRzN_pfH7P1eZK9Tv5ZZzBVCf8ct1La1s9lUXsRSg,104193
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=PuTSoalzqWJigJXmDkkgaiGR-n5ieR47SU4lOvOkLZk,96513
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.3.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
+ copyparty-1.18.3.dist-info/METADATA,sha256=IwBSD6NubWRSBqPVhBc5i8L_1PLB_kwzvoz3SMVlUyo,165941
115
+ copyparty-1.18.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ copyparty-1.18.3.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
+ copyparty-1.18.3.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
+ copyparty-1.18.3.dist-info/RECORD,,