copyparty 1.18.6__py3-none-any.whl → 1.18.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -53,13 +53,13 @@ from .util import (
53
53
  PYFTPD_VER,
54
54
  RAM_AVAIL,
55
55
  RAM_TOTAL,
56
+ RE_ANSI,
56
57
  SQLITE_VER,
57
58
  UNPLICATIONS,
58
59
  URL_BUG,
59
60
  URL_PRJ,
60
61
  Daemon,
61
62
  align_tab,
62
- ansi_re,
63
63
  b64enc,
64
64
  dedent,
65
65
  has_resource,
@@ -161,7 +161,7 @@ def lprint(*a , **ka ) :
161
161
  txt = " ".join(unicode(x) for x in a) + eol
162
162
  printed.append(txt)
163
163
  if not VT100:
164
- txt = ansi_re.sub("", txt)
164
+ txt = RE_ANSI.sub("", txt)
165
165
 
166
166
  print(txt, end="", **ka)
167
167
 
@@ -1045,6 +1045,8 @@ def add_upload(ap):
1045
1045
  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, ...)")
1046
1046
  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)")
1047
1047
  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)")
1048
+ ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)")
1049
+ ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)")
1048
1050
  ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
1049
1051
  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)")
1050
1052
  ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
@@ -1547,7 +1549,7 @@ def add_ui(ap, retry):
1547
1549
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1548
1550
  ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
1549
1551
  ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
1550
- 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)")
1552
+ ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1551
1553
  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")
1552
1554
  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)")
1553
1555
  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, 18, 6)
3
+ VERSION = (1, 18, 7)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 28)
5
+ BUILD_DT = (2025, 7, 30)
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
@@ -33,6 +33,7 @@ from .util import (
33
33
  afsenc,
34
34
  get_df,
35
35
  humansize,
36
+ json_hesc,
36
37
  min_ex,
37
38
  odfusion,
38
39
  read_utf8,
@@ -63,6 +64,25 @@ if PY2:
63
64
 
64
65
 
65
66
  LEELOO_DALLAS = "leeloo_dallas"
67
+ ##
68
+ ## you might be curious what Leeloo Dallas is doing here, so let me explain:
69
+ ##
70
+ ## certain daemonic tasks, namely:
71
+ ## * deletion of expired files, running on a timer
72
+ ## * deletion of sidecar files, initiated by plugins
73
+ ## need to skip the usual permission-checks to do their thing,
74
+ ## so we let Leeloo handle these
75
+ ##
76
+ ## and also, the smb-server has really shitty support for user-accounts
77
+ ## so one popular way to avoid issues is by running copyparty without users;
78
+ ## this makes all smb-clients identify as LD to gain unrestricted access
79
+ ##
80
+ ## Leeloo, being a fictional character from The Fifth Element,
81
+ ## obviously does not exist and will never be able to access any copyparty
82
+ ## instances from the outside (the username is rejected at every entrypoint)
83
+ ##
84
+ ## thanks for coming to my ted talk
85
+
66
86
 
67
87
  SEE_LOG = "see log for details"
68
88
  SEESLOG = " (see serverlog for details)"
@@ -114,6 +134,8 @@ class Lim(object):
114
134
  self.reg = None # up2k registry
115
135
 
116
136
  self.chmod_d = 0o755
137
+ self.uid = self.gid = -1
138
+ self.chown = False
117
139
 
118
140
  self.nups = {} # num tracker
119
141
  self.bups = {} # byte tracker list
@@ -276,6 +298,8 @@ class Lim(object):
276
298
  # no branches yet; make one
277
299
  sub = os.path.join(path, "0")
278
300
  bos.mkdir(sub, self.chmod_d)
301
+ if self.chown:
302
+ os.chown(sub, self.uid, self.gid)
279
303
  else:
280
304
  # try newest branch only
281
305
  sub = os.path.join(path, str(dirs[-1]))
@@ -291,6 +315,8 @@ class Lim(object):
291
315
  # make a branch
292
316
  sub = os.path.join(path, str(dirs[-1] + 1))
293
317
  bos.mkdir(sub, self.chmod_d)
318
+ if self.chown:
319
+ os.chown(sub, self.uid, self.gid)
294
320
  ret = self.dive(sub, lvs - 1)
295
321
  if ret is None:
296
322
  raise Pebkac(500, "rotation bug")
@@ -2153,7 +2179,7 @@ class AuthSrv(object):
2153
2179
  if vf not in vol.flags:
2154
2180
  vol.flags[vf] = getattr(self.args, ga)
2155
2181
 
2156
- zs = "forget_ip nrand tail_who u2abort u2ow ups_who zip_who"
2182
+ zs = "forget_ip gid nrand tail_who u2abort u2ow uid ups_who zip_who"
2157
2183
  for k in zs.split():
2158
2184
  if k in vol.flags:
2159
2185
  vol.flags[k] = int(vol.flags[k])
@@ -2190,8 +2216,17 @@ class AuthSrv(object):
2190
2216
  if (is_d and zi != 0o755) or not is_d:
2191
2217
  free_umask = True
2192
2218
 
2219
+ vol.flags.pop("chown", None)
2220
+ if vol.flags["uid"] != -1 or vol.flags["gid"] != -1:
2221
+ vol.flags["chown"] = True
2222
+ vol.flags.pop("fperms", None)
2223
+ if "chown" in vol.flags or vol.flags.get("chmod_f"):
2224
+ vol.flags["fperms"] = True
2193
2225
  if vol.lim:
2194
2226
  vol.lim.chmod_d = vol.flags["chmod_d"]
2227
+ vol.lim.chown = "chown" in vol.flags
2228
+ vol.lim.uid = vol.flags["uid"]
2229
+ vol.lim.gid = vol.flags["gid"]
2195
2230
 
2196
2231
  if vol.flags.get("og"):
2197
2232
  self.args.uqe = True
@@ -2742,7 +2777,7 @@ class AuthSrv(object):
2742
2777
  "lifetime": vn.js_ls["lifetime"],
2743
2778
  "u2sort": self.args.u2sort,
2744
2779
  }
2745
- vn.js_htm = json.dumps(js_htm)
2780
+ vn.js_htm = json_hesc(json.dumps(js_htm))
2746
2781
 
2747
2782
  vols = list(vfs.all_nodes.values())
2748
2783
  if enshare:
copyparty/bos/bos.py CHANGED
@@ -6,8 +6,11 @@ import os
6
6
  from ..util import SYMTIME, fsdec, fsenc
7
7
  from . import path as path
8
8
 
9
- _ = (path,)
10
- __all__ = ["path"]
9
+ MKD_755 = {"chmod_d": 0o755}
10
+ MKD_700 = {"chmod_d": 0o700}
11
+
12
+ _ = (path, MKD_755, MKD_700)
13
+ __all__ = ["path", "MKD_755", "MKD_700"]
11
14
 
12
15
  # grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c
13
16
  # printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')"
@@ -17,11 +20,15 @@ def chmod(p , mode ) :
17
20
  return os.chmod(fsenc(p), mode)
18
21
 
19
22
 
23
+ def chown(p , uid , gid ) :
24
+ return os.chown(fsenc(p), uid, gid)
25
+
26
+
20
27
  def listdir(p = ".") :
21
28
  return [fsdec(x) for x in os.listdir(fsenc(p))]
22
29
 
23
30
 
24
- def makedirs(name , mode = 0o755, exist_ok = True) :
31
+ def makedirs(name , vf = MKD_755, exist_ok = True) :
25
32
  # os.makedirs does 777 for all but leaf; this does mode on all
26
33
  todo = []
27
34
  bname = fsenc(name)
@@ -34,9 +41,13 @@ def makedirs(name , mode = 0o755, exist_ok = True) :
34
41
  if not exist_ok:
35
42
  os.mkdir(bname) # to throw
36
43
  return False
44
+ mode = vf["chmod_d"]
45
+ chown = "chown" in vf
37
46
  for zb in todo[::-1]:
38
47
  try:
39
48
  os.mkdir(zb, mode)
49
+ if chown:
50
+ os.chown(zb, vf["uid"], vf["gid"])
40
51
  except:
41
52
  if os.path.isdir(zb):
42
53
  continue
copyparty/cfg.py CHANGED
@@ -114,6 +114,8 @@ def vf_vmap() :
114
114
  "unlist",
115
115
  "u2abort",
116
116
  "u2ts",
117
+ "uid",
118
+ "gid",
117
119
  "ups_who",
118
120
  "zip_who",
119
121
  "zipmaxn",
@@ -175,6 +177,8 @@ flagcats = {
175
177
  "nodupe": "rejects existing files (instead of linking/cloning them)",
176
178
  "chmod_d=755": "unix-permission for new dirs/folders",
177
179
  "chmod_f=644": "unix-permission for new files",
180
+ "uid=573": "change owner of new files/folders to unix-user 573",
181
+ "gid=999": "change owner of new files/folders to unix-group 999",
178
182
  "sparse": "force use of sparse files, mainly for s3-backed storage",
179
183
  "nosparse": "deny use of sparse files, mainly for slow storage",
180
184
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
copyparty/ftpd.py CHANGED
@@ -31,6 +31,7 @@ from .util import (
31
31
  relchk,
32
32
  runhook,
33
33
  sanitize_fn,
34
+ set_fperms,
34
35
  vjoin,
35
36
  wunlink,
36
37
  )
@@ -258,8 +259,8 @@ class FtpFs(AbstractedFS):
258
259
  wunlink(self.log, ap, VF_CAREFUL)
259
260
 
260
261
  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"])
262
+ if w and "fperms" in vfs.flags:
263
+ set_fperms(ret, vfs.flags)
263
264
 
264
265
  return ret
265
266
 
@@ -293,8 +294,7 @@ class FtpFs(AbstractedFS):
293
294
 
294
295
  def mkdir(self, path ) :
295
296
  ap, vfs, _ = self.rv2a(path, w=True)
296
- chmod = vfs.flags["chmod_d"]
297
- bos.makedirs(ap, chmod) # filezilla expects this
297
+ bos.makedirs(ap, vf=vfs.flags) # filezilla expects this
298
298
 
299
299
  def listdir(self, path ) :
300
300
  vpath = join(self.cwd, path)
copyparty/httpcli.py CHANGED
@@ -33,7 +33,7 @@ except:
33
33
 
34
34
  from .__init__ import ANYWIN, PY2, RES, TYPE_CHECKING, EnvParams, unicode
35
35
  from .__version__ import S_VERSION
36
- from .authsrv import VFS # typechk
36
+ from .authsrv import LEELOO_DALLAS, VFS # typechk
37
37
  from .bos import bos
38
38
  from .star import StreamTar
39
39
  from .stolen.qrcodegen import QrCode, qr2svg
@@ -79,8 +79,10 @@ from .util import (
79
79
  hidedir,
80
80
  html_bescape,
81
81
  html_escape,
82
+ html_sh_esc,
82
83
  humansize,
83
84
  ipnorm,
85
+ json_hesc,
84
86
  justcopy,
85
87
  load_resource,
86
88
  loadpy,
@@ -103,6 +105,7 @@ from .util import (
103
105
  sanitize_vpath,
104
106
  sendfile_kern,
105
107
  sendfile_py,
108
+ set_fperms,
106
109
  stat_resource,
107
110
  ub64dec,
108
111
  ub64enc,
@@ -617,6 +620,9 @@ class HttpCli(object):
617
620
  ) or self.args.idp_h_key in self.headers
618
621
 
619
622
  if trusted_key and trusted_xff:
623
+ if idp_usr.lower() == LEELOO_DALLAS:
624
+ self.loud_reply("send her back", status=403)
625
+ return False
620
626
  self.asrv.idp_checkin(self.conn.hsrv.broker, idp_usr, idp_grp)
621
627
  else:
622
628
  if not trusted_key:
@@ -1105,15 +1111,18 @@ class HttpCli(object):
1105
1111
  else:
1106
1112
  return True
1107
1113
 
1114
+ host = self.host.lower()
1115
+ if host.startswith("["):
1116
+ if "]:" in host:
1117
+ host = host.split("]:")[0] + "]"
1118
+ else:
1119
+ host = host.split(":")[0]
1120
+
1108
1121
  oh = self.out_headers
1109
1122
  origin = origin.lower()
1110
- good_origins = self.args.acao + [
1111
- "%s://%s"
1112
- % (
1113
- "https" if self.is_https else "http",
1114
- self.host.lower().split(":")[0],
1115
- )
1116
- ]
1123
+ proto = "https" if self.is_https else "http"
1124
+ good_origins = self.args.acao + ["%s://%s" % (proto, host)]
1125
+
1117
1126
  if "pw" in ih or re.sub(r"(:[0-9]{1,5})?/?$", "", origin) in good_origins:
1118
1127
  good_origin = True
1119
1128
  bad_hdrs = ("",)
@@ -1570,6 +1579,18 @@ class HttpCli(object):
1570
1579
  self.log("inaccessible: %r" % ("/" + self.vpath,))
1571
1580
  raise Pebkac(401, "authenticate")
1572
1581
 
1582
+ if "quota-available-bytes" in props and not self.args.nid:
1583
+ bfree, btot, _ = get_df(vn.realpath, False)
1584
+ if btot:
1585
+ df = {
1586
+ "quota-available-bytes": str(bfree),
1587
+ "quota-used-bytes": str(btot - bfree),
1588
+ }
1589
+ else:
1590
+ df = {}
1591
+ else:
1592
+ df = {}
1593
+
1573
1594
  fgen = itertools.chain([topdir], fgen)
1574
1595
  vtop = vjoin(self.args.R, vjoin(vn.vpath, rem))
1575
1596
 
@@ -1612,6 +1633,9 @@ class HttpCli(object):
1612
1633
  ap = os.path.join(tap, x["vp"])
1613
1634
  pvs["getcontenttype"] = html_escape(guess_mime(rp, ap))
1614
1635
  pvs["getcontentlength"] = str(st.st_size)
1636
+ elif df:
1637
+ pvs.update(df)
1638
+ df = {}
1615
1639
 
1616
1640
  for k, v in pvs.items():
1617
1641
  if k not in props:
@@ -2060,7 +2084,7 @@ class HttpCli(object):
2060
2084
  fdir, fn = os.path.split(fdir)
2061
2085
  rem, _ = vsplit(rem)
2062
2086
 
2063
- bos.makedirs(fdir, vfs.flags["chmod_d"])
2087
+ bos.makedirs(fdir, vf=vfs.flags)
2064
2088
 
2065
2089
  open_ka = {"fun": open}
2066
2090
  open_a = ["wb", self.args.iobuf]
@@ -2117,9 +2141,7 @@ class HttpCli(object):
2117
2141
  if nameless:
2118
2142
  fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
2119
2143
 
2120
- params = {"suffix": suffix, "fdir": fdir}
2121
- if "chmod_f" in vfs.flags:
2122
- params["chmod"] = vfs.flags["chmod_f"]
2144
+ params = {"suffix": suffix, "fdir": fdir, "vf": vfs.flags}
2123
2145
  if self.args.nw:
2124
2146
  params = {}
2125
2147
  fn = os.devnull
@@ -2167,7 +2189,7 @@ class HttpCli(object):
2167
2189
  if self.args.nw:
2168
2190
  fn = os.devnull
2169
2191
  else:
2170
- bos.makedirs(fdir, vfs.flags["chmod_d"])
2192
+ bos.makedirs(fdir, vf=vfs.flags)
2171
2193
  path = os.path.join(fdir, fn)
2172
2194
  if not nameless:
2173
2195
  self.vpath = vjoin(self.vpath, fn)
@@ -2299,7 +2321,7 @@ class HttpCli(object):
2299
2321
  if self.args.hook_v:
2300
2322
  log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
2301
2323
  fdir, self.vpath, fn, (vfs, rem) = x
2302
- bos.makedirs(fdir, vfs.flags["chmod_d"])
2324
+ bos.makedirs(fdir, vf=vfs.flags)
2303
2325
  path2 = os.path.join(fdir, fn)
2304
2326
  atomic_move(self.log, path, path2, vfs.flags)
2305
2327
  path = path2
@@ -2584,7 +2606,7 @@ class HttpCli(object):
2584
2606
  dst = vfs.canonical(rem)
2585
2607
  try:
2586
2608
  if not bos.path.isdir(dst):
2587
- bos.makedirs(dst, vfs.flags["chmod_d"])
2609
+ bos.makedirs(dst, vf=vfs.flags)
2588
2610
  except OSError as ex:
2589
2611
  self.log("makedirs failed %r" % (dst,))
2590
2612
  if not bos.path.isdir(dst):
@@ -3027,7 +3049,7 @@ class HttpCli(object):
3027
3049
  raise Pebkac(405, 'folder "/%s" already exists' % (vpath,))
3028
3050
 
3029
3051
  try:
3030
- bos.makedirs(fn, vfs.flags["chmod_d"])
3052
+ bos.makedirs(fn, vf=vfs.flags)
3031
3053
  except OSError as ex:
3032
3054
  if ex.errno == errno.EACCES:
3033
3055
  raise Pebkac(500, "the server OS denied write-access")
@@ -3068,8 +3090,8 @@ class HttpCli(object):
3068
3090
 
3069
3091
  with open(fsenc(fn), "wb") as f:
3070
3092
  f.write(b"`GRUNNUR`\n")
3071
- if "chmod_f" in vfs.flags:
3072
- os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3093
+ if "fperms" in vfs.flags:
3094
+ set_fperms(f, vfs.flags)
3073
3095
 
3074
3096
  vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
3075
3097
  self.redirect(vpath, "?edit")
@@ -3143,7 +3165,7 @@ class HttpCli(object):
3143
3165
  )
3144
3166
  upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
3145
3167
  if not nullwrite:
3146
- bos.makedirs(fdir_base, vfs.flags["chmod_d"])
3168
+ bos.makedirs(fdir_base, vf=vfs.flags)
3147
3169
 
3148
3170
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
3149
3171
  zs = self.uparam.get("want") or self.headers.get("accept") or ""
@@ -3176,7 +3198,7 @@ class HttpCli(object):
3176
3198
  if rnd:
3177
3199
  fname = rand_name(fdir, fname, rnd)
3178
3200
 
3179
- open_args = {"fdir": fdir, "suffix": suffix}
3201
+ open_args = {"fdir": fdir, "suffix": suffix, "vf": vfs.flags}
3180
3202
 
3181
3203
  if "replace" in self.uparam:
3182
3204
  if not self.can_delete:
@@ -3238,11 +3260,8 @@ class HttpCli(object):
3238
3260
  else:
3239
3261
  open_args["fdir"] = fdir
3240
3262
 
3241
- if "chmod_f" in vfs.flags:
3242
- open_args["chmod"] = vfs.flags["chmod_f"]
3243
-
3244
3263
  if p_file and not nullwrite:
3245
- bos.makedirs(fdir, vfs.flags["chmod_d"])
3264
+ bos.makedirs(fdir, vf=vfs.flags)
3246
3265
 
3247
3266
  # reserve destination filename
3248
3267
  f, fname = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
@@ -3346,7 +3365,7 @@ class HttpCli(object):
3346
3365
  if nullwrite:
3347
3366
  fdir = ap2 = ""
3348
3367
  else:
3349
- bos.makedirs(fdir, vfs.flags["chmod_d"])
3368
+ bos.makedirs(fdir, vf=vfs.flags)
3350
3369
  atomic_move(self.log, abspath, ap2, vfs.flags)
3351
3370
  abspath = ap2
3352
3371
  sz = bos.path.getsize(abspath)
@@ -3467,8 +3486,8 @@ class HttpCli(object):
3467
3486
  ft = "{}:{}".format(self.ip, self.addr[1])
3468
3487
  ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
3469
3488
  f.write(ft.encode("utf-8"))
3470
- if "chmod_f" in vfs.flags:
3471
- os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3489
+ if "fperms" in vfs.flags:
3490
+ set_fperms(f, vfs.flags)
3472
3491
  except Exception as ex:
3473
3492
  suf = "\nfailed to write the upload report: {}".format(ex)
3474
3493
 
@@ -3518,7 +3537,7 @@ class HttpCli(object):
3518
3537
  lim = vfs.get_dbv(rem)[0].lim
3519
3538
  if lim:
3520
3539
  fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker)
3521
- bos.makedirs(fp, vfs.flags["chmod_d"])
3540
+ bos.makedirs(fp, vf=vfs.flags)
3522
3541
 
3523
3542
  fp = os.path.join(fp, fn)
3524
3543
  rem = "{}/{}".format(rp, fn).strip("/")
@@ -3586,15 +3605,17 @@ class HttpCli(object):
3586
3605
  zs = ub64enc(zb).decode("ascii")[:24].lower()
3587
3606
  dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
3588
3607
  self.log("moving old version to %s/%s" % (dp, mfile2))
3589
- if bos.makedirs(dp, vfs.flags["chmod_d"]):
3608
+ if bos.makedirs(dp, vf=vfs.flags):
3590
3609
  with open(os.path.join(dp, "dir.txt"), "wb") as f:
3591
3610
  f.write(afsenc(vrd))
3592
- if "chmod_f" in vfs.flags:
3593
- os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3611
+ if "fperms" in vfs.flags:
3612
+ set_fperms(f, vfs.flags)
3594
3613
  elif hist_cfg == "s":
3595
3614
  dp = os.path.join(mdir, ".hist")
3596
3615
  try:
3597
3616
  bos.mkdir(dp, vfs.flags["chmod_d"])
3617
+ if "chown" in vfs.flags:
3618
+ bos.chown(dp, vfs.flags["uid"], vfs.flags["gid"])
3598
3619
  hidedir(dp)
3599
3620
  except:
3600
3621
  pass
@@ -3632,8 +3653,8 @@ class HttpCli(object):
3632
3653
  wunlink(self.log, fp, vfs.flags)
3633
3654
 
3634
3655
  with open(fsenc(fp), "wb", self.args.iobuf) as f:
3635
- if "chmod_f" in vfs.flags:
3636
- os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3656
+ if "fperms" in vfs.flags:
3657
+ set_fperms(f, vfs.flags)
3637
3658
  sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp)
3638
3659
 
3639
3660
  if lim:
@@ -4870,11 +4891,8 @@ class HttpCli(object):
4870
4891
  else:
4871
4892
  rip = host
4872
4893
 
4873
- # safer than html_escape/quotep since this avoids both XSS and shell-stuff
4874
- pw = re.sub(r"[<>&$?`\"']", "_", self.pw or "hunter2")
4875
- vp = re.sub(r"[<>&$?`\"']", "_", self.uparam["hc"] or "").lstrip("/")
4876
- pw = pw.replace(" ", "%20")
4877
- vp = vp.replace(" ", "%20")
4894
+ vp = (self.uparam["hc"] or "").lstrip("/")
4895
+ pw = self.pw or "hunter2"
4878
4896
  if pw in self.asrv.sesa:
4879
4897
  pw = "hunter2"
4880
4898
 
@@ -4883,14 +4901,14 @@ class HttpCli(object):
4883
4901
  args=self.args,
4884
4902
  accs=bool(self.asrv.acct),
4885
4903
  s="s" if self.is_https else "",
4886
- rip=rip,
4887
- ep=ep,
4888
- vp=vp,
4889
- rvp=vjoin(self.args.R, vp),
4890
- host=host,
4891
- hport=hport,
4904
+ rip=html_sh_esc(rip),
4905
+ ep=html_sh_esc(ep),
4906
+ vp=html_sh_esc(vp),
4907
+ rvp=html_sh_esc(vjoin(self.args.R, vp)),
4908
+ host=html_sh_esc(host),
4909
+ hport=html_sh_esc(hport),
4892
4910
  aname=aname,
4893
- pw=pw,
4911
+ pw=html_sh_esc(pw),
4894
4912
  )
4895
4913
  self.reply(html.encode("utf-8"))
4896
4914
  return True
@@ -5552,7 +5570,7 @@ class HttpCli(object):
5552
5570
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
5553
5571
  return True
5554
5572
 
5555
- html = self.j2s("rups", this=self, v=jtxt)
5573
+ html = self.j2s("rups", this=self, v=json_hesc(jtxt))
5556
5574
  self.reply(html.encode("utf-8"), status=200)
5557
5575
  return True
5558
5576
 
@@ -5616,15 +5634,15 @@ class HttpCli(object):
5616
5634
  raise Pebkac(500, "sqlite3 not found on server; sharing is disabled")
5617
5635
  raise Pebkac(500, "server busy, cannot create share; please retry in a bit")
5618
5636
 
5637
+ skey = self.uparam.get("skey") or self.vpath.split("/")[-1]
5638
+
5619
5639
  if self.args.shr_v:
5620
- self.log("handle_eshare: " + self.req)
5640
+ self.log("handle_eshare: " + skey)
5621
5641
 
5622
5642
  cur = idx.get_shr()
5623
5643
  if not cur:
5624
5644
  raise Pebkac(400, "huh, sharing must be disabled in the server config...")
5625
5645
 
5626
- skey = self.vpath.split("/")[-1]
5627
-
5628
5646
  rows = cur.execute("select un, t1 from sh where k = ?", (skey,)).fetchall()
5629
5647
  un = rows[0][0] if rows and rows[0] else ""
5630
5648
 
@@ -6133,13 +6151,13 @@ class HttpCli(object):
6133
6151
  self.log("#wow #whoa")
6134
6152
 
6135
6153
  if not self.args.nid:
6136
- free, total, _ = get_df(abspath, False)
6137
- if total is not None:
6154
+ free, total, zs = get_df(abspath, False)
6155
+ if total:
6138
6156
  h1 = humansize(free or 0)
6139
6157
  h2 = humansize(total)
6140
6158
  srv_info.append("{} free of {}".format(h1, h2))
6141
- elif free is not None:
6142
- srv_info.append(humansize(free, True) + " free")
6159
+ elif zs:
6160
+ self.log("diskfree(%r): %s" % (abspath, zs), 3)
6143
6161
 
6144
6162
  srv_infot = "</span> // <span>".join(srv_info)
6145
6163
 
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, vfs2.flags["chmod_d"])
320
+ bos.makedirs(ap2, vf=vfs2.flags)
321
321
  except:
322
322
  pass
323
323
 
copyparty/svchub.py CHANGED
@@ -45,6 +45,7 @@ from .util import (
45
45
  HAVE_PSUTIL,
46
46
  HAVE_SQLITE3,
47
47
  HAVE_ZMQ,
48
+ RE_ANSI,
48
49
  URL_BUG,
49
50
  UTC,
50
51
  VERSIONS,
@@ -54,7 +55,6 @@ from .util import (
54
55
  HMaccas,
55
56
  ODict,
56
57
  alltrace,
57
- ansi_re,
58
58
  build_netmap,
59
59
  expat_ver,
60
60
  gzip,
@@ -1399,9 +1399,9 @@ class SvcHub(object):
1399
1399
  if self.no_ansi:
1400
1400
  fmt = "%s %-21s %s\n"
1401
1401
  if "\033" in msg:
1402
- msg = ansi_re.sub("", msg)
1402
+ msg = RE_ANSI.sub("", msg)
1403
1403
  if "\033" in src:
1404
- src = ansi_re.sub("", src)
1404
+ src = RE_ANSI.sub("", src)
1405
1405
  elif c:
1406
1406
  if isinstance(c, int):
1407
1407
  msg = "\033[3%sm%s\033[0m" % (c, msg)
copyparty/tftpd.py CHANGED
@@ -45,6 +45,7 @@ from .util import (
45
45
  exclude_dotfiles,
46
46
  min_ex,
47
47
  runhook,
48
+ set_fperms,
48
49
  undot,
49
50
  vjoin,
50
51
  vsplit,
@@ -385,8 +386,8 @@ class Tftpd(object):
385
386
  a = (self.args.iobuf,)
386
387
 
387
388
  ret = open(ap, mode, *a, **ka)
388
- if wr and "chmod_f" in vfs.flags:
389
- os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
389
+ if wr and "fperms" in vfs.flags:
390
+ set_fperms(ret, vfs.flags)
390
391
 
391
392
  return ret
392
393
 
@@ -395,7 +396,9 @@ class Tftpd(object):
395
396
  if "*" not in vfs.axs.uwrite:
396
397
  yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
397
398
 
398
- return bos.mkdir(ap, vfs.flags["chmod_d"])
399
+ bos.mkdir(ap, vfs.flags["chmod_d"])
400
+ if "chown" in vfs.flags:
401
+ bos.chown(ap, vfs.flags["uid"], vfs.flags["gid"])
399
402
 
400
403
  def _unlink(self, vpath ) :
401
404
  # return bos.unlink(self._v2a("stat", vpath, *a)[1])
copyparty/th_srv.py CHANGED
@@ -266,8 +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
- chmod = 0o700 if self.args.free_umask else 0o755
270
- bos.makedirs(os.path.join(thdir, "w"), chmod)
269
+ chmod = bos.MKD_700 if self.args.free_umask else bos.MKD_755
270
+ bos.makedirs(os.path.join(thdir, "w"), vf=chmod)
271
271
 
272
272
  inf_path = os.path.join(thdir, "dir.txt")
273
273
  if not bos.path.exists(inf_path):
copyparty/up2k.py CHANGED
@@ -911,7 +911,7 @@ class Up2k(object):
911
911
  for vol in vols:
912
912
  try:
913
913
  # mkdir gonna happen at snap anyways;
914
- bos.makedirs(vol.realpath, vol.flags["chmod_d"])
914
+ bos.makedirs(vol.realpath, vf=vol.flags)
915
915
  dir_is_empty(self.log_func, not self.args.no_scandir, vol.realpath)
916
916
  except Exception as ex:
917
917
  self.volstate[vol.vpath] = "OFFLINE (cannot access folder)"
@@ -3293,7 +3293,7 @@ class Up2k(object):
3293
3293
  reg,
3294
3294
  "up2k._get_volsize",
3295
3295
  )
3296
- bos.makedirs(ap2, vfs.flags["chmod_d"])
3296
+ bos.makedirs(ap2, vf=vfs.flags)
3297
3297
  vfs.lim.nup(cj["addr"])
3298
3298
  vfs.lim.bup(cj["addr"], cj["size"])
3299
3299
 
@@ -3429,7 +3429,7 @@ class Up2k(object):
3429
3429
  "wb",
3430
3430
  fdir=fdir,
3431
3431
  suffix="-%.6f-%s" % (ts, dip),
3432
- chmod=vf.get("chmod_f", -1),
3432
+ vf=vf,
3433
3433
  )
3434
3434
  f.close()
3435
3435
  return ret
@@ -4283,7 +4283,7 @@ class Up2k(object):
4283
4283
  self.log(t, 1)
4284
4284
  raise Pebkac(405, t)
4285
4285
 
4286
- bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4286
+ bos.makedirs(os.path.dirname(dabs), vf=dvn.flags)
4287
4287
 
4288
4288
  c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
4289
4289
  svn_dbv.realpath, srem_dbv
@@ -4458,7 +4458,10 @@ class Up2k(object):
4458
4458
  vp = vjoin(dvp, rem)
4459
4459
  try:
4460
4460
  dvn, drem = self.vfs.get(vp, uname, False, True)
4461
- bos.mkdir(dvn.canonical(drem), dvn.flags["chmod_d"])
4461
+ dap = dvn.canonical(drem)
4462
+ bos.mkdir(dap, dvn.flags["chmod_d"])
4463
+ if "chown" in dvn.flags:
4464
+ bos.chown(dap, dvn.flags["uid"], dvn.flags["gid"])
4462
4465
  except:
4463
4466
  pass
4464
4467
 
@@ -4528,7 +4531,7 @@ class Up2k(object):
4528
4531
 
4529
4532
  is_xvol = svn.realpath != dvn.realpath
4530
4533
 
4531
- bos.makedirs(os.path.dirname(dabs), dvn.flags["chmod_d"])
4534
+ bos.makedirs(os.path.dirname(dabs), vf=dvn.flags)
4532
4535
 
4533
4536
  if is_dirlink:
4534
4537
  dlabs = absreal(sabs)
@@ -5038,7 +5041,7 @@ class Up2k(object):
5038
5041
  "wb",
5039
5042
  fdir=pdir,
5040
5043
  suffix="-%.6f-%s" % (job["t0"], dip),
5041
- chmod=vf.get("chmod_f", -1),
5044
+ vf=vf,
5042
5045
  )
5043
5046
  try:
5044
5047
  abspath = djoin(pdir, job["tnam"])
copyparty/util.py CHANGED
@@ -155,7 +155,9 @@ except:
155
155
  HAVE_PSUTIL = False
156
156
 
157
157
  try:
158
- if os.environ.get("PRTY_NO_MAGIC"):
158
+ if os.environ.get("PRTY_NO_MAGIC") or (
159
+ ANYWIN and not os.environ.get("PRTY_FORCE_MAGIC")
160
+ ):
159
161
  raise Exception()
160
162
 
161
163
  import magic
@@ -220,7 +222,18 @@ except:
220
222
  BITNESS = struct.calcsize("P") * 8
221
223
 
222
224
 
223
- ansi_re = re.compile("\033\\[[^mK]*[mK]")
225
+ RE_ANSI = re.compile("\033\\[[^mK]*[mK]")
226
+ RE_HTML_SH = re.compile(r"[<>&$?`\"';]")
227
+ RE_CTYPE = re.compile(r"^content-type: *([^; ]+)", re.IGNORECASE)
228
+ RE_CDISP = re.compile(r"^content-disposition: *([^; ]+)", re.IGNORECASE)
229
+ RE_CDISP_FIELD = re.compile(
230
+ r'^content-disposition:(?: *|.*; *)name="([^"]+)"', re.IGNORECASE
231
+ )
232
+ RE_CDISP_FILE = re.compile(
233
+ r'^content-disposition:(?: *|.*; *)filename="(.*)"', re.IGNORECASE
234
+ )
235
+ RE_MEMTOTAL = re.compile("^MemTotal:.* kB")
236
+ RE_MEMAVAIL = re.compile("^MemAvailable:.* kB")
224
237
 
225
238
 
226
239
  BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
@@ -465,11 +478,11 @@ def read_ram() :
465
478
  with open("/proc/meminfo", "rb", 0x10000) as f:
466
479
  zsl = f.read(0x10000).decode("ascii", "replace").split("\n")
467
480
 
468
- p = re.compile("^MemTotal:.* kB")
481
+ p = RE_MEMTOTAL
469
482
  zs = next((x for x in zsl if p.match(x)))
470
483
  a = int((int(zs.split()[1]) / 0x100000) * 100) / 100
471
484
 
472
- p = re.compile("^MemAvailable:.* kB")
485
+ p = RE_MEMAVAIL
473
486
  zs = next((x for x in zsl if p.match(x)))
474
487
  b = int((int(zs.split()[1]) / 0x100000) * 100) / 100
475
488
  except:
@@ -1503,7 +1516,8 @@ def ren_open(fname , *args , **kwargs ) :
1503
1516
  fun = kwargs.pop("fun", open)
1504
1517
  fdir = kwargs.pop("fdir", None)
1505
1518
  suffix = kwargs.pop("suffix", None)
1506
- chmod = kwargs.pop("chmod", -1)
1519
+ vf = kwargs.pop("vf", None)
1520
+ fperms = vf and "fperms" in vf
1507
1521
 
1508
1522
  if fname == os.devnull:
1509
1523
  return fun(fname, *args, **kwargs), fname
@@ -1546,11 +1560,11 @@ def ren_open(fname , *args , **kwargs ) :
1546
1560
  fp2 = os.path.join(fdir, fp2)
1547
1561
  with open(fsenc(fp2), "wb") as f2:
1548
1562
  f2.write(orig_name.encode("utf-8"))
1549
- if chmod >= 0:
1550
- os.fchmod(f2.fileno(), chmod)
1563
+ if fperms:
1564
+ set_fperms(f2, vf)
1551
1565
 
1552
- if chmod >= 0:
1553
- os.fchmod(f.fileno(), chmod)
1566
+ if fperms:
1567
+ set_fperms(f, vf)
1554
1568
 
1555
1569
  return f, fname
1556
1570
 
@@ -1612,14 +1626,10 @@ class MultipartParser(object):
1612
1626
  self.args = args
1613
1627
  self.headers = http_headers
1614
1628
 
1615
- self.re_ctype = re.compile(r"^content-type: *([^; ]+)", re.IGNORECASE)
1616
- self.re_cdisp = re.compile(r"^content-disposition: *([^; ]+)", re.IGNORECASE)
1617
- self.re_cdisp_field = re.compile(
1618
- r'^content-disposition:(?: *|.*; *)name="([^"]+)"', re.IGNORECASE
1619
- )
1620
- self.re_cdisp_file = re.compile(
1621
- r'^content-disposition:(?: *|.*; *)filename="(.*)"', re.IGNORECASE
1622
- )
1629
+ self.re_ctype = RE_CTYPE
1630
+ self.re_cdisp = RE_CDISP
1631
+ self.re_cdisp_field = RE_CDISP_FIELD
1632
+ self.re_cdisp_file = RE_CDISP_FILE
1623
1633
 
1624
1634
  self.boundary = b""
1625
1635
  self.gen = None
@@ -2158,6 +2168,16 @@ def find_prefix(ips , cidrs ) :
2158
2168
  return ret
2159
2169
 
2160
2170
 
2171
+ def html_sh_esc(s ) :
2172
+ s = re.sub(RE_HTML_SH, "_", s).replace(" ", "%20")
2173
+ s = s.replace("\r", "_").replace("\n", "_")
2174
+ return s
2175
+
2176
+
2177
+ def json_hesc(s ) :
2178
+ return s.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")
2179
+
2180
+
2161
2181
  def html_escape(s , quot = False, crlf = False) :
2162
2182
  """html.escape but also newlines"""
2163
2183
  s = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
@@ -2477,6 +2497,14 @@ def lsof(log , abspath ) :
2477
2497
  log("lsof failed; " + min_ex(), 3)
2478
2498
 
2479
2499
 
2500
+ def set_fperms(f , vf ) :
2501
+ fno = f.fileno()
2502
+ if "chmod_f" in vf:
2503
+ os.fchmod(fno, vf["chmod_f"])
2504
+ if "chown" in vf:
2505
+ os.fchown(fno, vf["uid"], vf["gid"])
2506
+
2507
+
2480
2508
  def _fs_mvrm(
2481
2509
  log , src , dst , atomic , flags
2482
2510
  ) :
@@ -2586,17 +2614,22 @@ def get_df(abspath , prune ) :
2586
2614
  if ANYWIN:
2587
2615
  abspath = fsdec(ap)
2588
2616
  bfree = ctypes.c_ulonglong(0)
2617
+ btotal = ctypes.c_ulonglong(0)
2618
+ bavail = ctypes.c_ulonglong(0)
2589
2619
  ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
2590
- ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
2620
+ ctypes.c_wchar_p(abspath),
2621
+ ctypes.pointer(bavail),
2622
+ ctypes.pointer(btotal),
2623
+ ctypes.pointer(bfree),
2591
2624
  )
2592
- return (bfree.value, None, "")
2625
+ return (bavail.value, btotal.value, "")
2593
2626
  else:
2594
2627
  sv = os.statvfs(ap)
2595
- free = sv.f_frsize * sv.f_bfree
2628
+ free = sv.f_frsize * sv.f_bavail
2596
2629
  total = sv.f_frsize * sv.f_blocks
2597
2630
  return (free, total, "")
2598
2631
  except Exception as ex:
2599
- return (None, None, repr(ex))
2632
+ return (0, 0, repr(ex))
2600
2633
 
2601
2634
 
2602
2635
  if not ANYWIN and not MACOS:
@@ -4068,7 +4101,14 @@ def load_resource(E , name , mode="rb") :
4068
4101
  stream = codecs.getreader(enc)(stream)
4069
4102
  return stream
4070
4103
 
4071
- return open(os.path.join(E.mod, name), mode, encoding=enc)
4104
+ ap = os.path.join(E.mod, name)
4105
+
4106
+ if PY2:
4107
+ import codecs
4108
+
4109
+ return codecs.open(ap, "r", encoding=enc) # type: ignore
4110
+
4111
+ return open(ap, mode, encoding=enc)
4072
4112
 
4073
4113
 
4074
4114
  class Pebkac(Exception):
@@ -109,8 +109,8 @@
109
109
  {%- for f in files %}
110
110
  <tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
111
111
  {%- if f.tags is defined %}
112
- {%- for k in taglist %}<td>{{ f.tags[k] }}</td>{%- endfor %}
113
- {%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
112
+ {%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
113
+ {%- endif %}<td>{{ f.ext|e }}</td><td>{{ f.dt }}</td></tr>
114
114
  {%- endfor %}
115
115
 
116
116
  </tbody>
Binary file
Binary file
Binary file
copyparty/web/svcs.html CHANGED
@@ -240,14 +240,26 @@
240
240
  <div class="os win">
241
241
  <h1>ShareX</h1>
242
242
 
243
- <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.1.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
243
+ <p>to upload screenshots using ShareX <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
244
+
245
+ <pre class="dl" name="copyparty.sxcu">
246
+ { "Version": "15.0.0", "Name": "copyparty",
247
+ "RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
248
+ "Headers": {
249
+ {% if accs %}"pw": "<b>{{ pw }}</b>", {% endif %}"accept": "url"
250
+ },
251
+ "DestinationType": "ImageUploader, TextUploader, FileUploader",
252
+ "Body": "MultipartFormData", "URL": "{response}",
253
+ "RequestMethod": "POST", "FileFormName": "f" }
254
+ </pre>
255
+
256
+ <p>for ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.1.1">v12</a> specifically, save this as <code>copyparty.sxcu</code> and run it:</p>
244
257
 
245
258
  <pre class="dl" name="copyparty.sxcu">
246
259
  { "Name": "copyparty",
247
260
  "RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
248
261
  "Headers": {
249
- {% if accs %}"pw": "<b>{{ pw }}</b>",{% endif %}
250
- "accept": "url"
262
+ {% if accs %}"pw": "<b>{{ pw }}</b>", {% endif %}"accept": "url"
251
263
  },
252
264
  "DestinationType": "ImageUploader, TextUploader, FileUploader",
253
265
  "FileFormName": "f" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.6
3
+ Version: 1.18.7
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
@@ -138,6 +138,7 @@ made in Norway 🇳🇴
138
138
  * [periodic rescan](#periodic-rescan) - filesystem monitoring
139
139
  * [upload rules](#upload-rules) - set upload rules using volflags
140
140
  * [compress uploads](#compress-uploads) - files can be autocompressed on upload
141
+ * [chmod and chown](#chmod-and-chown) - per-volume filesystem-permissions and ownership
141
142
  * [other flags](#other-flags)
142
143
  * [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
143
144
  * [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
@@ -1707,6 +1708,26 @@ some examples,
1707
1708
  allows (but does not force) gz compression if client uploads to `/inc?pk` or `/inc?gz` or `/inc?gz=4`
1708
1709
 
1709
1710
 
1711
+ ## chmod and chown
1712
+
1713
+ per-volume filesystem-permissions and ownership
1714
+
1715
+ by default:
1716
+ * all folders are chmod 755
1717
+ * files are usually chmod 644 (umask-defined)
1718
+ * user/group is whatever copyparty is running as
1719
+
1720
+ this can be configured per-volume:
1721
+ * volflag `chmod_f` sets file permissions; default=`644` (usually)
1722
+ * volflag `chmod_d` sets directory permissions; default=`755`
1723
+ * volflag `uid` sets the owner user-id
1724
+ * volflag `gid` sets the owner group-id
1725
+
1726
+ notes:
1727
+ * `gid` can only be set to one of the groups which the copyparty process is a member of
1728
+ * `uid` can only be set if copyparty is running as root (i appreciate your faith)
1729
+
1730
+
1710
1731
  ## other flags
1711
1732
 
1712
1733
  * `:c,magic` enables filetype detection for nameless uploads, same as `--magic`
@@ -2284,6 +2305,7 @@ force-enable features with known issues on your OS/env by setting any of the fo
2284
2305
  | env-var | what it does |
2285
2306
  | ------------------------ | ------------ |
2286
2307
  | `PRTY_FORCE_MP` | force-enable multiprocessing (real multithreading) on MacOS and other broken platforms |
2308
+ | `PRTY_FORCE_MAGIC` | use [magic](https://pypi.org/project/python-magic/) on Windows (you will segfault) |
2287
2309
 
2288
2310
 
2289
2311
  # packages
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=4aJw_Mt3eSNMV8sJ95Nh4ris-tBUYhCOV094Rnxa5Xo,2651
2
- copyparty/__main__.py,sha256=Cy0ivEsRSPkVaXVLN8MAb0eXoqAWUimJtpAlfkFfiAQ,127229
3
- copyparty/__version__.py,sha256=_F7FWcR2IUumXhoBLRfrzOu5Lo_sQzMEt-ZOMo5BN_k,249
4
- copyparty/authsrv.py,sha256=dF1K3Tx5Semm6JWJAPr3Nv-H6LzpH_lPvoHYYvVDE1U,120995
2
+ copyparty/__main__.py,sha256=vs0_3Ba4k4BbYF34V3loxPB0Dds_T1akBr44mpXv3TQ,127567
3
+ copyparty/__version__.py,sha256=BmHBWBUUrc62D7mlf1y2spt-lRJLom0MewDTgdJGeaQ,249
4
+ copyparty/authsrv.py,sha256=luOWk3_6ZvCJI8bp6UmBPSeyQT02HH5Nf6vnakCwzGI,122483
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=nP8SVI3DMuhmbG1xSHex-965KmhDwI6G1kLqa28nZas,15557
10
+ copyparty/cfg.py,sha256=THceFupFmsZWF8iJKDDHR_XUEU3TNQgDRJB50iLiC1k,15734
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=xDDWixo5O2HT8wlexXQ0QRazU_fYERIJ4yF0mtu9wD0,18141
14
- copyparty/httpcli.py,sha256=O6YnFXBKYt17Y4SpGOR-wezrFrQHvzrQ_ywKvpKN7Lo,231164
13
+ copyparty/ftpd.py,sha256=S0w6iMR8AlzLc_Aqn-TKuUJ-vNbmeQF6SQs614-NFOE,18107
14
+ copyparty/httpcli.py,sha256=hoc9p9iJ_WegTrUbLTAOkwwqltnvw35eQLO0_ZzvN3I,231645
15
15
  copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
16
  copyparty/httpsrv.py,sha256=MCNjOEH_xM2qXCLGcoN6W0RhWlikv68-zBx0nICIheU,18864
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=ljqkiUblKzmLF6NVSXBRcFvy61j8QdVOjXToi2xQcyM,19939
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=jeKGkVLG9-Q0zPNDZdIwb_O59388rvGzG2cQQ2qPXpk,14659
23
+ copyparty/smbd.py,sha256=Czo8SRkkl4ndCwEUe9Cbr8v0YOnyQHzubGSguPizuTc,14651
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=x64KviAMgIoo60L49D4qxij0Npb5pyUjuofpOjTV4o0,49083
27
+ copyparty/svchub.py,sha256=LVhCj0IxHBlGdDbX-At_wAQioQ2MQD2WPbofHzA08B0,49083
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
29
  copyparty/tcpsrv.py,sha256=BCOqlT_mRu1ibHJpPzvf9c4h83AnIMEfd8nBBednCCg,20484
30
- copyparty/tftpd.py,sha256=sNBMqazIB37t3jwhv_F1tr0lHFsiRX-nCsICM8nbkvc,14170
30
+ copyparty/tftpd.py,sha256=QuPcdx77gLmEpit3lLc0x4Px6BrBBKJpJl4VqINc5O8,14254
31
31
  copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=INLaV1_NH1VKtBx9XaqyJbZNAj2ZhfcQwz0_5Y5fyiY,32744
32
+ copyparty/th_srv.py,sha256=S6ChazjXwXevUxkAajwIwHzA16PyNIwv6kYRFakvLmY,32759
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
- copyparty/up2k.py,sha256=6VjhlYG3jDTqjdFmrQRtrV_fpzeK3L9K-OEcYU3Mo9I,179076
35
- copyparty/util.py,sha256=AtY7rVW64F6xJ00FbM7f43YBiVI6ctjtjUeb6zahAeY,104493
34
+ copyparty/up2k.py,sha256=n1ZSEopnFmXbjPEU_B84T3i2nCXvIuLuY308uM4UaJQ,179150
35
+ copyparty/util.py,sha256=4Q7fgR4GDjiTUIRjOSEhmZGscKSimgeICGQFcVRyHRU,105446
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- copyparty/bos/bos.py,sha256=ZwpTla_mFpoN_BbGn9h0dEn7wpZ607QJ7uYyxWJMcmw,1953
37
+ copyparty/bos/bos.py,sha256=DYt5mJJNt-935rU7HRm8kt_whpcVSI0uSphvD7PXrJo,2247
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
@@ -56,8 +56,8 @@ copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=MxRofvhXjmUN7RtXtC17_9AlROVNUT-66WwJ_pHLY9c,8258
58
58
  copyparty/web/browser.css.gz,sha256=29D3F4uB-VMd6uJo-SxWAwLfn08jcETYZm0SOJFJLAE,11847
59
- copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=CUpLvqCMFQXpRFf_T15Ie0tJsCqmdJzJK-HYdD6Ea_o,96908
59
+ copyparty/web/browser.html,sha256=FKyez1jN3I7iG7VlAjZDtNi6PenYue22mahThVv4BTA,4792
60
+ copyparty/web/browser.js.gz,sha256=yMy_s-cgUH8Vo0cos3lIZTqaBxyfJpphOGK0oM02jAc,111316
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
@@ -77,11 +77,11 @@ copyparty/web/rups.html,sha256=iPuz53jBT_mIWIfl1yrjjg5-P7oO2ada6fTFq8PgjGk,1479
77
77
  copyparty/web/rups.js.gz,sha256=nvvcG8L-fkm7zkhjnlTGhBp_KD0j08mtHEW0sB7zy-Y,854
78
78
  copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,547
79
79
  copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,2545
80
- copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,940
80
+ copyparty/web/shares.js.gz,sha256=NQzrF57cikU38NzoaWQhbrINWkQBZhu1sH0jAC2SESQ,967
81
81
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
82
82
  copyparty/web/splash.html,sha256=0MvDe1lKfGqczi7d4nKjWjG0cRVyvs8J6sDEj3DCPSI,6376
83
- copyparty/web/splash.js.gz,sha256=xMl4Rly-ykhTx7LCI2MK1DpCblFWmFU9uA1rgivgen8,2771
84
- copyparty/web/svcs.html,sha256=wiwcbmGi3vypVdVtD2FXYJYLGoExqLacRzJjBNKyBpE,14251
83
+ copyparty/web/splash.js.gz,sha256=6ag8Szmjd_S1jr2D0-DgubM1F6Fq7dUU7A8E-qBY2gI,3702
84
+ copyparty/web/svcs.html,sha256=mamJdq0hsmHqG2BQsf9jg8G9bAl338wUhUZ2WtXOlGQ,14865
85
85
  copyparty/web/svcs.js.gz,sha256=AYatNKyT_bKRWX8sb3WD_iujBY3L4P7HWBrsuMctsLs,722
86
86
  copyparty/web/ui.css.gz,sha256=e3iIflzddmjoyPrun_1jsu9j7fbdonNQLyhEE2oKKOQ,2819
87
87
  copyparty/web/up2k.js.gz,sha256=_uOZzORAFO91SG3TUHd9xhKhAIXwL3fUFBNEUKnEVHY,24812
@@ -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.6.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
- copyparty-1.18.6.dist-info/METADATA,sha256=reukmKR4ZWFmItgVokiPVrt_WeE-bBIQD9Se71FLRDA,166911
115
- copyparty-1.18.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
- copyparty-1.18.6.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
- copyparty-1.18.6.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
- copyparty-1.18.6.dist-info/RECORD,,
113
+ copyparty-1.18.7.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
+ copyparty-1.18.7.dist-info/METADATA,sha256=bpeGIM2brZAQDtodeY1BoLDFdRs2xm04ssv6qHf6zUM,167745
115
+ copyparty-1.18.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ copyparty-1.18.7.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
+ copyparty-1.18.7.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
+ copyparty-1.18.7.dist-info/RECORD,,