copyparty 1.16.11__py3-none-any.whl → 1.16.13__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
@@ -1175,6 +1175,7 @@ def add_webdav(ap):
1175
1175
  ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
1176
1176
  ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
1177
1177
  ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
1178
+ ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of tricky user-agents which expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank")
1178
1179
 
1179
1180
 
1180
1181
  def add_tftp(ap):
@@ -1255,7 +1256,8 @@ def add_optouts(ap):
1255
1256
  ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
1256
1257
  ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
1257
1258
  ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
1258
- ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
1259
+ ap2.add_argument("--zip-who", metavar="LVL", type=int, default=3, help="who can download as zip/tar? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=zip_who)\n\033[1;31mWARNING:\033[0m if a nested volume has a more restrictive value than a parent volume, then this will be \033[33mignored\033[0m if the download is initiated from the parent, more lenient volume")
1260
+ ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar; same as \033[33m--zip-who=0\033[0m")
1259
1261
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1260
1262
  ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
1261
1263
  ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
@@ -1386,7 +1388,7 @@ def add_transcoding(ap):
1386
1388
 
1387
1389
  def add_rss(ap):
1388
1390
  ap2 = ap.add_argument_group('RSS options')
1389
- ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental)")
1391
+ ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
1390
1392
  ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
1391
1393
  ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
1392
1394
  ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files")
@@ -1477,7 +1479,9 @@ def add_ui(ap, retry):
1477
1479
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1478
1480
  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)")
1479
1481
  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")
1482
+ 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)")
1480
1483
  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])")
1484
+ ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1481
1485
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1482
1486
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
1483
1487
  ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 11)
3
+ VERSION = (1, 16, 13)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 1, 27)
5
+ BUILD_DT = (2025, 2, 13)
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
@@ -1282,10 +1282,10 @@ class AuthSrv(object):
1282
1282
  # one or more bools before the final flag; eat them
1283
1283
  n1, uname = uname.split(",", 1)
1284
1284
  for _, vp, _, _ in vols:
1285
- self._read_volflag(flags[vp], n1, True, False)
1285
+ self._read_volflag(vp, flags[vp], n1, True, False)
1286
1286
 
1287
1287
  for _, vp, _, _ in vols:
1288
- self._read_volflag(flags[vp], uname, cval, False)
1288
+ self._read_volflag(vp, flags[vp], uname, cval, False)
1289
1289
 
1290
1290
  return
1291
1291
 
@@ -1372,20 +1372,34 @@ class AuthSrv(object):
1372
1372
 
1373
1373
  def _read_volflag(
1374
1374
  self,
1375
+ vpath ,
1375
1376
  flags ,
1376
1377
  name ,
1377
1378
  value ,
1378
1379
  is_list ,
1379
1380
  ) :
1381
+ if name not in flagdescs:
1382
+ name = name.lower()
1383
+
1384
+ # volflags are snake_case, but a leading dash is the removal operator
1385
+ if name not in flagdescs and "-" in name[1:]:
1386
+ name = name[:1] + name[1:].replace("-", "_")
1387
+
1380
1388
  desc = flagdescs.get(name.lstrip("-"), "?").replace("\n", " ")
1381
1389
 
1390
+ if not name:
1391
+ self._e("└─unreadable-line")
1392
+ t = "WARNING: the config for volume [/%s] indicated that a volflag was to be defined, but the volflag name was blank"
1393
+ self.log(t % (vpath,), 3)
1394
+ return
1395
+
1382
1396
  if re.match("^-[^-]+$", name):
1383
1397
  t = "└─unset volflag [{}] ({})"
1384
1398
  self._e(t.format(name[1:], desc))
1385
1399
  flags[name] = True
1386
1400
  return
1387
1401
 
1388
- zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1402
+ zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1389
1403
  if name not in zs.split():
1390
1404
  if value is True:
1391
1405
  t = "└─add volflag [{}] = {} ({})"
@@ -1508,6 +1522,14 @@ class AuthSrv(object):
1508
1522
  if not mount and not self.args.idp_h_usr:
1509
1523
  # -h says our defaults are CWD at root and read/write for everyone
1510
1524
  axs = AXS(["*"], ["*"], None, None)
1525
+ if os.path.exists("/z/initcfg"):
1526
+ t = "Read-access has been disabled due to failsafe: Docker detected, but the config does not define any volumes. 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 all of /w/ by adding the following arguments to the docker container: -v .::rw"
1527
+ self.log(t, 1)
1528
+ axs = AXS()
1529
+ elif self.args.c:
1530
+ 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"
1531
+ self.log(t, 1)
1532
+ axs = AXS()
1511
1533
  vfs = VFS(self.log_func, absreal("."), "", axs, {})
1512
1534
  elif "" not in mount:
1513
1535
  # there's volumes but no root; make root inaccessible
@@ -1542,6 +1564,17 @@ class AuthSrv(object):
1542
1564
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1543
1565
  vol.root = vfs
1544
1566
 
1567
+ zs = "neversymlink"
1568
+ k_ign = set(zs.split())
1569
+ for vol in vfs.all_vols.values():
1570
+ unknown_flags = set()
1571
+ for k, v in vol.flags.items():
1572
+ if k not in flagdescs and k not in k_ign:
1573
+ unknown_flags.add(k)
1574
+ if unknown_flags:
1575
+ t = "WARNING: the config for volume [/%s] has unrecognized volflags; will ignore: '%s'"
1576
+ self.log(t % (vol.vpath, "', '".join(unknown_flags)), 3)
1577
+
1545
1578
  enshare = self.args.shr
1546
1579
  shr = enshare[1:-1]
1547
1580
  shrs = enshare[1:]
@@ -1907,7 +1940,7 @@ class AuthSrv(object):
1907
1940
  if k not in vol.flags:
1908
1941
  vol.flags[k] = getattr(self.args, k)
1909
1942
 
1910
- for k in ("nrand", "u2abort"):
1943
+ for k in ("nrand", "u2abort", "ups_who", "zip_who"):
1911
1944
  if k in vol.flags:
1912
1945
  vol.flags[k] = int(vol.flags[k])
1913
1946
 
@@ -1959,8 +1992,10 @@ class AuthSrv(object):
1959
1992
 
1960
1993
  # append additive args from argv to volflags
1961
1994
  hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
1962
- for name in "mtp on404 on403".split() + hooks:
1963
- self._read_volflag(vol.flags, name, getattr(self.args, name), True)
1995
+ for name in "ext_th mtp on404 on403".split() + hooks:
1996
+ self._read_volflag(
1997
+ vol.vpath, vol.flags, name, getattr(self.args, name), True
1998
+ )
1964
1999
 
1965
2000
  for hn in hooks:
1966
2001
  cmds = vol.flags.get(hn)
@@ -1988,6 +2023,16 @@ class AuthSrv(object):
1988
2023
  ncmds.append(ocmd)
1989
2024
  vol.flags[hn] = ncmds
1990
2025
 
2026
+ ext_th = vol.flags["ext_th_d"] = {}
2027
+ etv = "(?)"
2028
+ try:
2029
+ for etv in vol.flags.get("ext_th") or []:
2030
+ k, v = etv.split("=")
2031
+ ext_th[k] = v
2032
+ except:
2033
+ t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
2034
+ self.log(t % (vol.vpath, etv), 3)
2035
+
1991
2036
  # d2d drops all database features for a volume
1992
2037
  for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
1993
2038
  if not vol.flags.get(grp, False):
@@ -2339,6 +2384,7 @@ class AuthSrv(object):
2339
2384
  "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
2340
2385
  }
2341
2386
  js_htm = {
2387
+ "SPINNER": self.args.spinner,
2342
2388
  "s_name": self.args.bname,
2343
2389
  "have_up2k_idx": "e2d" in vf,
2344
2390
  "have_acode": not self.args.no_acode,
@@ -2348,6 +2394,7 @@ class AuthSrv(object):
2348
2394
  "have_del": not self.args.no_del,
2349
2395
  "have_unpost": int(self.args.unpost),
2350
2396
  "have_emp": self.args.emp,
2397
+ "ext_th": vf.get("ext_th_d") or {},
2351
2398
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2352
2399
  "sba_md": vf.get("md_sba") or "",
2353
2400
  "sba_lg": vf.get("lg_sba") or "",
@@ -2751,7 +2798,9 @@ class AuthSrv(object):
2751
2798
  zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
2752
2799
  lst = set(zs.split())
2753
2800
  askip = set("a v c vc cgen exp_lg exp_md theme".split())
2754
- fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
2801
+
2802
+ t = "exp_lg exp_md ext_th_d mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
2803
+ fskip = set(t.split())
2755
2804
 
2756
2805
  # keymap from argv to vflag
2757
2806
  amap = vf_bmap()
copyparty/cfg.py CHANGED
@@ -5,6 +5,9 @@ from __future__ import print_function, unicode_literals
5
5
  zs = "a c e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vp e2vu ed emp i j lo mcr mte mth mtm mtp nb nc nid nih nth nw p q s ss sss v z zv"
6
6
  onedash = set(zs.split())
7
7
 
8
+ # verify that all volflags are documented here:
9
+ # grep volflag= __main__.py | sed -r 's/.*volflag=//;s/\).*//' | sort | uniq | while IFS= read -r x; do grep -E "\"$x(=[^ \"]+)?\": \"" cfg.py || printf '%s\n' "$x"; done
10
+
8
11
 
9
12
  def vf_bmap() :
10
13
  """argv-to-volflag: simple bools"""
@@ -94,6 +97,7 @@ def vf_vmap() :
94
97
  "u2abort",
95
98
  "u2ts",
96
99
  "ups_who",
100
+ "zip_who",
97
101
  ):
98
102
  ret[k] = k
99
103
  return ret
@@ -105,6 +109,7 @@ def vf_cmap() :
105
109
  for k in (
106
110
  "exp_lg",
107
111
  "exp_md",
112
+ "ext_th",
108
113
  "mte",
109
114
  "mth",
110
115
  "mtp",
@@ -178,8 +183,11 @@ flagcats = {
178
183
  "e2dsa": "scans all folders for new files on startup; also sets -e2d",
179
184
  "e2t": "enable multimedia indexing; makes it possible to search for tags",
180
185
  "e2ts": "scan existing files for tags on startup; also sets -e2t",
181
- "e2tsa": "delete all metadata from DB (full rescan); also sets -e2ts",
186
+ "e2tsr": "delete all metadata from DB (full rescan); also sets -e2ts",
182
187
  "d2ts": "disables metadata collection for existing files",
188
+ "e2v": "verify integrity on startup by hashing files and comparing to db",
189
+ "e2vu": "when e2v fails, update the db (assume on-disk files are good)",
190
+ "e2vp": "when e2v fails, panic and quit copyparty",
183
191
  "d2ds": "disables onboot indexing, overrides -e2ds*",
184
192
  "d2t": "disables metadata collection, overrides -e2t*",
185
193
  "d2v": "disables file verification, overrides -e2v*",
@@ -199,6 +207,8 @@ flagcats = {
199
207
  "srch_excl": "exclude search results with URL matching this regex",
200
208
  },
201
209
  'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
210
+ "mte=artist,title": "media-tags to index/display",
211
+ "mth=fmt,res,ac": "media-tags to hide by default",
202
212
  "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
203
213
  "mtp=ahash,vhash=media-hash.py": "collects two tags at once",
204
214
  },
@@ -212,6 +222,7 @@ flagcats = {
212
222
  "crop": "center-cropping (y/n/fy/fn)",
213
223
  "th3x": "3x resolution (y/n/fy/fn)",
214
224
  "convt": "conversion timeout in seconds",
225
+ "ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
215
226
  },
216
227
  "handlers\n(better explained in --help-handlers)": {
217
228
  "on404=PY": "handle 404s by executing PY file",
@@ -234,8 +245,12 @@ flagcats = {
234
245
  "grid": "show grid/thumbnails by default",
235
246
  "gsel": "select files in grid by ctrl-click",
236
247
  "sort": "default sort order",
248
+ "nsort": "natural-sort of leading digits in filenames",
249
+ "hsortn": "number of sort-rules to add to media URLs",
237
250
  "unlist": "dont list files matching REGEX",
238
251
  "html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
252
+ "tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
253
+ "nodirsz": "don't show total folder size",
239
254
  "robots": "allows indexing by search engines (default)",
240
255
  "norobots": "kindly asks search engines to leave",
241
256
  "no_sb_md": "disable js sandbox for markdown files",
@@ -248,10 +263,33 @@ flagcats = {
248
263
  "lg_sba": "value of iframe allow-prop for *logue-sandbox",
249
264
  "nohtml": "return html and markdown as text/html",
250
265
  },
266
+ "opengraph (discord embeds)": {
267
+ "og": "enable OG (disables hotlinking)",
268
+ "og_site": "sitename; defaults to --name, disable with '-'",
269
+ "og_desc": "description text for all files; disable with '-'",
270
+ "og_th=jf": "thumbnail format; j / jf / jf3 / w / w3 / ...",
271
+ "og_title_a": "audio title format; default: {{ artist }} - {{ title }}",
272
+ "og_title_v": "video title format; default: {{ title }}",
273
+ "og_title_i": "image title format; default: {{ title }}",
274
+ "og_title=foo": "fallback title if there's nothing in the db",
275
+ "og_s_title": "force default title; do not read from tags",
276
+ "og_tpl": "custom html; see --og-tpl in --help",
277
+ "og_no_head": "you want to add tags manually with og_tpl",
278
+ "og_ua": "if defined: only send OG html if useragent matches this regex",
279
+ },
280
+ "textfiles": {
281
+ "exp": "enable textfile expansion; see --help-exp",
282
+ "exp_md": "placeholders to expand in markdown files; see --help",
283
+ "exp_lg": "placeholders to expand in prologue/epilogue; see --help",
284
+ },
251
285
  "others": {
252
286
  "dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
253
287
  "fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
254
288
  "fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
289
+ "rss": "allow '?rss' URL suffix (experimental)",
290
+ "ups_who=2": "restrict viewing the list of recent uploads",
291
+ "zip_who=2": "restrict access to download-as-zip/tar",
292
+ "nopipe": "disable race-the-beam (download unfinished uploads)",
255
293
  "mv_retry": "ms-windows: timeout for renaming busy files",
256
294
  "rm_retry": "ms-windows: timeout for deleting busy files",
257
295
  "davauth": "ask webdav clients to login for all folders",
@@ -261,3 +299,4 @@ flagcats = {
261
299
 
262
300
 
263
301
  flagdescs = {k.split("=")[0]: v for tab in flagcats.values() for k, v in tab.items()}
302
+
copyparty/httpcli.py CHANGED
@@ -186,7 +186,7 @@ class HttpCli(object):
186
186
  self.is_vproxied = False
187
187
  self.in_hdr_recv = True
188
188
  self.headers = {}
189
- self.mode = " "
189
+ self.mode = " " # http verb
190
190
  self.req = " "
191
191
  self.http_ver = ""
192
192
  self.hint = ""
@@ -726,10 +726,10 @@ class HttpCli(object):
726
726
  return self.handle_unlock() and self.keepalive
727
727
  elif self.mode == "MKCOL":
728
728
  return self.handle_mkcol() and self.keepalive
729
- elif self.mode == "MOVE":
730
- return self.handle_move() and self.keepalive
729
+ elif self.mode in ("MOVE", "COPY"):
730
+ return self.handle_cpmv() and self.keepalive
731
731
  else:
732
- raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
732
+ raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode))
733
733
 
734
734
  except Exception as ex:
735
735
  if not isinstance(ex, Pebkac):
@@ -1228,6 +1228,20 @@ class HttpCli(object):
1228
1228
  else:
1229
1229
  return self.tx_404(True)
1230
1230
  else:
1231
+ vfs = self.asrv.vfs
1232
+ if (
1233
+ not vfs.nodes
1234
+ and not vfs.axs.uread
1235
+ and not vfs.axs.uwrite
1236
+ and not vfs.axs.uget
1237
+ and not vfs.axs.uhtml
1238
+ and not vfs.axs.uadmin
1239
+ ):
1240
+ t = "<h2>access denied due to failsafe; check server log</h2>"
1241
+ html = self.j2s("splash", this=self, msg=t)
1242
+ self.reply(html.encode("utf-8", "replace"), 500)
1243
+ return True
1244
+
1231
1245
  if self.vpath:
1232
1246
  ptn = self.args.nonsus_urls
1233
1247
  if not ptn or not ptn.search(self.vpath):
@@ -1754,6 +1768,12 @@ class HttpCli(object):
1754
1768
  if "%" in self.req:
1755
1769
  self.log(" `-- %r" % (self.vpath,))
1756
1770
 
1771
+ if self.args.no_dav:
1772
+ raise Pebkac(405, "WebDAV is disabled in server config")
1773
+
1774
+ if not self.can_write:
1775
+ raise Pebkac(401, "authenticate")
1776
+
1757
1777
  try:
1758
1778
  return self._mkdir(self.vpath, True)
1759
1779
  except Pebkac as ex:
@@ -1763,14 +1783,35 @@ class HttpCli(object):
1763
1783
  self.reply(b"", ex.code)
1764
1784
  return True
1765
1785
 
1766
- def handle_move(self) :
1786
+ def handle_cpmv(self) :
1767
1787
  dst = self.headers["destination"]
1768
- dst = re.sub("^https?://[^/]+", "", dst).lstrip()
1788
+
1789
+ # dolphin (kioworker/6.10) "webdav://127.0.0.1:3923/a/b.txt"
1790
+ dst = re.sub("^[a-zA-Z]+://[^/]+", "", dst).lstrip()
1791
+
1792
+ if self.is_vproxied and dst.startswith(self.args.SRS):
1793
+ dst = dst[len(self.args.RS) :]
1794
+
1795
+ if self.do_log:
1796
+ self.log("%s %s --//> %s @%s" % (self.mode, self.req, dst, self.uname))
1797
+ if "%" in self.req:
1798
+ self.log(" `-- %r" % (self.vpath,))
1799
+
1800
+ if self.args.no_dav:
1801
+ raise Pebkac(405, "WebDAV is disabled in server config")
1802
+
1769
1803
  dst = unquotep(dst)
1770
- if not self._mv(self.vpath, dst.lstrip("/")):
1771
- return False
1772
1804
 
1773
- return True
1805
+ # overwrite=True is default; rfc4918 9.8.4
1806
+ overwrite = self.headers.get("overwrite", "").lower() != "f"
1807
+
1808
+ try:
1809
+ fun = self._cp if self.mode == "COPY" else self._mv
1810
+ return fun(self.vpath, dst.lstrip("/"), overwrite)
1811
+ except Pebkac as ex:
1812
+ if ex.code == 403:
1813
+ ex.code = 401
1814
+ raise
1774
1815
 
1775
1816
  def _applesan(self) :
1776
1817
  if self.args.dav_mac or "Darwin/" not in self.ua:
@@ -4263,8 +4304,14 @@ class HttpCli(object):
4263
4304
  rem ,
4264
4305
  items ,
4265
4306
  ) :
4266
- if self.args.no_zip:
4267
- raise Pebkac(400, "not enabled in server config")
4307
+ lvl = vn.flags["zip_who"]
4308
+ if self.args.no_zip or not lvl:
4309
+ raise Pebkac(400, "download-as-zip/tar is disabled in server config")
4310
+ elif lvl <= 1 and not self.can_admin:
4311
+ raise Pebkac(400, "download-as-zip/tar is admin-only on this server")
4312
+ elif lvl <= 2 and self.uname in ("", "*"):
4313
+ t = "you must be authenticated to download-as-zip/tar on this server"
4314
+ raise Pebkac(400, t)
4268
4315
 
4269
4316
  logmsg = "{:4} {} ".format("", self.req)
4270
4317
  self.keepalive = False
@@ -4768,9 +4815,12 @@ class HttpCli(object):
4768
4815
  # that the client is not a graphical browser
4769
4816
  if (
4770
4817
  rc == 403
4771
- and not self.pw
4772
- and not self.ua.startswith("Mozilla/")
4818
+ and self.uname == "*"
4773
4819
  and "sec-fetch-site" not in self.headers
4820
+ and (
4821
+ not self.ua.startswith("Mozilla/")
4822
+ or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
4823
+ )
4774
4824
  ):
4775
4825
  rc = 401
4776
4826
  self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
@@ -4804,7 +4854,7 @@ class HttpCli(object):
4804
4854
 
4805
4855
  def scanvol(self) :
4806
4856
  if not self.can_admin:
4807
- raise Pebkac(403, "not allowed for user " + self.uname)
4857
+ raise Pebkac(403, "'scanvol' not allowed for user " + self.uname)
4808
4858
 
4809
4859
  if self.args.no_rescan:
4810
4860
  raise Pebkac(403, "the rescan feature is disabled in server config")
@@ -4827,7 +4877,7 @@ class HttpCli(object):
4827
4877
  raise Pebkac(400, "only config files ('cfg') can be reloaded rn")
4828
4878
 
4829
4879
  if not self.avol:
4830
- raise Pebkac(403, "not allowed for user " + self.uname)
4880
+ raise Pebkac(403, "'reload' not allowed for user " + self.uname)
4831
4881
 
4832
4882
  if self.args.no_reload:
4833
4883
  raise Pebkac(403, "the reload feature is disabled in server config")
@@ -4837,7 +4887,7 @@ class HttpCli(object):
4837
4887
 
4838
4888
  def tx_stack(self) :
4839
4889
  if not self.avol and not [x for x in self.wvol if x in self.rvol]:
4840
- raise Pebkac(403, "not allowed for user " + self.uname)
4890
+ raise Pebkac(403, "'stack' not allowed for user " + self.uname)
4841
4891
 
4842
4892
  if self.args.no_stack:
4843
4893
  raise Pebkac(403, "the stackdump feature is disabled in server config")
@@ -5130,7 +5180,7 @@ class HttpCli(object):
5130
5180
  adm = "*" in vol.axs.uadmin or self.uname in vol.axs.uadmin
5131
5181
  dots = "*" in vol.axs.udot or self.uname in vol.axs.udot
5132
5182
 
5133
- lvl = int(vol.flags["ups_who"])
5183
+ lvl = vol.flags["ups_who"]
5134
5184
  if not lvl:
5135
5185
  continue
5136
5186
  elif lvl == 1 and not adm:
@@ -5399,7 +5449,9 @@ class HttpCli(object):
5399
5449
 
5400
5450
  def handle_rm(self, req ) :
5401
5451
  if not req and not self.can_delete:
5402
- raise Pebkac(403, "not allowed for user " + self.uname)
5452
+ if self.mode == "DELETE" and self.uname == "*":
5453
+ raise Pebkac(401, "authenticate") # webdav
5454
+ raise Pebkac(403, "'delete' not allowed for user " + self.uname)
5403
5455
 
5404
5456
  if self.args.no_del:
5405
5457
  raise Pebkac(403, "the delete feature is disabled in server config")
@@ -5433,14 +5485,22 @@ class HttpCli(object):
5433
5485
  if not dst:
5434
5486
  raise Pebkac(400, "need dst vpath")
5435
5487
 
5436
- return self._mv(self.vpath, dst.lstrip("/"))
5488
+ return self._mv(self.vpath, dst.lstrip("/"), False)
5437
5489
 
5438
- def _mv(self, vsrc , vdst ) :
5490
+ def _mv(self, vsrc , vdst , overwrite ) :
5439
5491
  if self.args.no_mv:
5440
5492
  raise Pebkac(403, "the rename/move feature is disabled in server config")
5441
5493
 
5442
- self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5443
- self.asrv.vfs.get(vdst, self.uname, False, True)
5494
+ # `handle_cpmv` will catch 403 from these and raise 401
5495
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5496
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5497
+
5498
+ if overwrite:
5499
+ dabs = dvn.canonical(drem)
5500
+ if bos.path.exists(dabs):
5501
+ self.log("overwriting %s" % (dabs,))
5502
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5503
+ wunlink(self.log, dabs, dvn.flags)
5444
5504
 
5445
5505
  x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
5446
5506
  self.loud_reply(x.get(), status=201)
@@ -5456,14 +5516,21 @@ class HttpCli(object):
5456
5516
  if not dst:
5457
5517
  raise Pebkac(400, "need dst vpath")
5458
5518
 
5459
- return self._cp(self.vpath, dst.lstrip("/"))
5519
+ return self._cp(self.vpath, dst.lstrip("/"), False)
5460
5520
 
5461
- def _cp(self, vsrc , vdst ) :
5521
+ def _cp(self, vsrc , vdst , overwrite ) :
5462
5522
  if self.args.no_cp:
5463
5523
  raise Pebkac(403, "the copy feature is disabled in server config")
5464
5524
 
5465
- self.asrv.vfs.get(vsrc, self.uname, True, False)
5466
- self.asrv.vfs.get(vdst, self.uname, False, True)
5525
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False)
5526
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5527
+
5528
+ if overwrite:
5529
+ dabs = dvn.canonical(drem)
5530
+ if bos.path.exists(dabs):
5531
+ self.log("overwriting %s" % (dabs,))
5532
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5533
+ wunlink(self.log, dabs, dvn.flags)
5467
5534
 
5468
5535
  x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
5469
5536
  self.loud_reply(x.get(), status=201)
copyparty/metrics.py CHANGED
@@ -18,7 +18,7 @@ class Metrics(object):
18
18
 
19
19
  def tx(self, cli ) :
20
20
  if not cli.avol:
21
- raise Pebkac(403, "not allowed for user " + cli.uname)
21
+ raise Pebkac(403, "'stats' not allowed for user " + cli.uname)
22
22
 
23
23
  args = cli.args
24
24
  if not args.stats:
copyparty/multicast.py CHANGED
@@ -160,6 +160,7 @@ class MCast(object):
160
160
  sck.settimeout(None)
161
161
  sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
162
162
  try:
163
+ # safe for this purpose; https://lwn.net/Articles/853637/
163
164
  sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
164
165
  except:
165
166
  pass
copyparty/svchub.py CHANGED
@@ -759,7 +759,7 @@ class SvcHub(object):
759
759
  vs = os.path.expandvars(os.path.expanduser(vs))
760
760
  setattr(al, k, vs)
761
761
 
762
- for k in "sus_urls nonsus_urls".split(" "):
762
+ for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
763
763
  vs = getattr(al, k)
764
764
  if not vs or vs == "no":
765
765
  setattr(al, k, None)
copyparty/up2k.py CHANGED
@@ -4610,7 +4610,7 @@ class Up2k(object):
4610
4610
 
4611
4611
  cur = self.cur.get(ptop)
4612
4612
  if not cur:
4613
- return None, None, None, None, None, None
4613
+ return None, None, None, None, "", None
4614
4614
 
4615
4615
  rd, fn = vsplit(vrem)
4616
4616
  q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1"
@@ -4623,7 +4623,7 @@ class Up2k(object):
4623
4623
  if hit:
4624
4624
  wark, ftime, fsize, ip, at = hit
4625
4625
  return cur, wark, ftime, fsize, ip, at
4626
- return cur, None, None, None, None, None
4626
+ return cur, None, None, None, "", None
4627
4627
 
4628
4628
  def _forget_file(
4629
4629
  self,
Binary file
Binary file
@@ -124,9 +124,7 @@
124
124
 
125
125
  </div>
126
126
 
127
- {%- if srv_info %}
128
127
  <div id="srv_info"><span>{{ srv_info }}</span></div>
129
- {%- endif %}
130
128
 
131
129
  <div id="widget"></div>
132
130
 
Binary file
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.11
3
+ Version: 1.16.13
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
@@ -201,6 +201,9 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
201
201
  * or if you are on android, [install copyparty in termux](#install-on-android)
202
202
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
203
203
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
204
+ * or if you don't trust copyparty yet and want to isolate it a little, then...
205
+ * ...maybe [prisonparty](./bin/prisonparty.sh) to create a tiny [chroot](https://wiki.archlinux.org/title/Chroot) (very portable),
206
+ * ...or [bubbleparty](./bin/bubbleparty.sh) to wrap it in [bubblewrap](https://github.com/containers/bubblewrap) (much better)
204
207
  * or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
205
208
  * docker has all deps built-in, so skip this step:
206
209
 
@@ -312,7 +315,7 @@ also see [comparison to similar software](./docs/versus.md)
312
315
  * ☑ search by name/path/date/size
313
316
  * ☑ [search by ID3-tags etc.](#searching)
314
317
  * client support
315
- * ☑ [folder sync](#folder-sync)
318
+ * ☑ [folder sync](#folder-sync) (one-way only; full sync will never be supported)
316
319
  * ☑ [curl-friendly](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png)
317
320
  * ☑ [opengraph](#opengraph) (discord embeds)
318
321
  * markdown
@@ -529,6 +532,40 @@ examples:
529
532
 
530
533
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
531
534
 
535
+ and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
536
+
537
+ ```yaml
538
+ [accounts]
539
+ u1: p1 # create account "u1" with password "p1"
540
+ u2: p2 # (note that comments must have
541
+ u3: p3 # two spaces before the # sign)
542
+
543
+ [/] # this URL will be mapped to...
544
+ /srv # ...this folder on the server filesystem
545
+ accs:
546
+ r: * # read-only for everyone, no account necessary
547
+
548
+ [/music] # create another volume at this URL,
549
+ /mnt/music # which is mapped to this folder
550
+ accs:
551
+ r: u1, u2 # only these accounts can read,
552
+ rw: u3 # and only u3 can read-write
553
+
554
+ [/inc]
555
+ /mnt/incoming
556
+ accs:
557
+ w: u1 # u1 can upload but not see/download any files,
558
+ rm: u2 # u2 can browse + move files out of this volume
559
+
560
+ [/i]
561
+ /mnt/ss
562
+ accs:
563
+ rw: u1 # u1 can read-write,
564
+ g: * # everyone can access files if they know the URL
565
+ flags:
566
+ fk: 4 # each file URL will have a 4-character password
567
+ ```
568
+
532
569
 
533
570
  ## shadowing
534
571
 
@@ -536,6 +573,8 @@ hiding specific subfolders by mounting another volume on top of them
536
573
 
537
574
  for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mnt` as the webroot, but another volume is mounted at `/web/certs` -- so visitors can only see the contents of `/mnt` and `/mnt/web` (at URLs `/` and `/web`), but not `/mnt/web/certs` because URL `/web/certs` is mapped to `/var/empty`
538
575
 
576
+ the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
577
+
539
578
 
540
579
  ## dotfiles
541
580
 
@@ -547,6 +586,19 @@ a client can request to see dotfiles in directory listings if global option `-ed
547
586
 
548
587
  dotfiles do not appear in search results unless one of the above is true, **and** the global option / volflag `dotsrch` is set
549
588
 
589
+ config file example, where the same permission to see dotfiles is given in two different ways just for reference:
590
+
591
+ ```yaml
592
+ [/foo]
593
+ /srv/foo
594
+ accs:
595
+ r.: ed # user "ed" has read-access + dot-access in this volume;
596
+ # dotfiles are visible in listings, but not in searches
597
+ flags:
598
+ dotsrch # dotfiles will now appear in search results too
599
+ dots # another way to let everyone see dotfiles in this vol
600
+ ```
601
+
550
602
 
551
603
  # the browser
552
604
 
@@ -669,6 +721,26 @@ enabling `multiselect` lets you click files to select them, and then shift-click
669
721
  * `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
670
722
  * the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
671
723
 
724
+ to show `/icons/exe.png` as the thumbnail for all .exe files, `--ext-th=exe=/icons/exe.png` (optionally as a volflag)
725
+
726
+ config file example:
727
+
728
+ ```yaml
729
+ [global]
730
+ no-thumb # disable ALL thumbnails and audio transcoding
731
+ no-vthumb # only disable video thumbnails
732
+
733
+ [/music]
734
+ /mnt/nas/music
735
+ accs:
736
+ r: * # everyone can read
737
+ flags:
738
+ dthumb # disable ALL thumbnails and audio transcoding
739
+ dvthumb # only disable video thumbnails
740
+ ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
741
+ th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
742
+ ```
743
+
672
744
 
673
745
  ## zip downloads
674
746
 
@@ -793,6 +865,14 @@ undo/delete accidental uploads using the `[🧯]` tab in the UI
793
865
 
794
866
  you can unpost even if you don't have regular move/delete access, however only for files uploaded within the past `--unpost` seconds (default 12 hours) and the server must be running with `-e2d`
795
867
 
868
+ config file example:
869
+
870
+ ```yaml
871
+ [global]
872
+ e2d # enable up2k database (remember uploads)
873
+ unpost: 43200 # 12 hours (default)
874
+ ```
875
+
796
876
 
797
877
  ### self-destruct
798
878
 
@@ -958,6 +1038,15 @@ will show uploader IP and upload-time if the visitor has the admin permission
958
1038
 
959
1039
  note that the [🧯 unpost](#unpost) feature is better suited for viewing *your own* recent uploads, as it includes the option to undo/delete them
960
1040
 
1041
+ config file example:
1042
+
1043
+ ```yaml
1044
+ [global]
1045
+ ups-when # everyone can see upload times
1046
+ ups-who: 1 # but only admins can see the list,
1047
+ # so ups-when doesn't take effect
1048
+ ```
1049
+
961
1050
 
962
1051
  ## media player
963
1052
 
@@ -1102,7 +1191,16 @@ using arguments or config files, or a mix of both:
1102
1191
 
1103
1192
  announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png)) -- `-z` enables both [mdns](#mdns) and [ssdp](#ssdp)
1104
1193
 
1105
- * `--z-on` / `--z-off`' limits the feature to certain networks
1194
+ * `--z-on` / `--z-off` limits the feature to certain networks
1195
+
1196
+ config file example:
1197
+
1198
+ ```yaml
1199
+ [global]
1200
+ z # enable all zeroconf features (mdns, ssdp)
1201
+ zm # only enables mdns (does nothing since we already have z)
1202
+ z-on: 192.168.0.0/16, 10.1.2.0/24 # restrict to certain subnets
1203
+ ```
1106
1204
 
1107
1205
 
1108
1206
  ### mdns
@@ -1243,7 +1341,7 @@ dependencies: `python3 -m pip install --user -U impacket==0.11.0`
1243
1341
 
1244
1342
  some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
1245
1343
  * not entirely confident that read-only is read-only
1246
- * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh)
1344
+ * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) or [bubbleparty](./bin/bubbleparty.sh)
1247
1345
  * account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb
1248
1346
  * [shadowing](#shadowing) probably works as expected but no guarantees
1249
1347
 
@@ -1329,6 +1427,18 @@ advantages of using symlinks (default):
1329
1427
 
1330
1428
  global-option `--xlink` / volflag `xlink` additionally enables deduplication across volumes, but this is probably buggy and not recommended
1331
1429
 
1430
+ config file example:
1431
+
1432
+ ```yaml
1433
+ [global]
1434
+ e2dsa # scan and index filesystem on startup
1435
+ dedup # symlink-based deduplication for all volumes
1436
+
1437
+ [/media]
1438
+ /mnt/nas/media
1439
+ flags:
1440
+ hardlinkonly # this vol does hardlinks instead of symlinks
1441
+ ```
1332
1442
 
1333
1443
 
1334
1444
  ## file indexing
@@ -1360,6 +1470,14 @@ note:
1360
1470
  * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
1361
1471
  * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
1362
1472
 
1473
+ config file example (these options are recommended btw):
1474
+
1475
+ ```yaml
1476
+ [global]
1477
+ e2dsa # scan and index all files in all volumes on startup
1478
+ e2ts # check newly-discovered or uploaded files for media tags
1479
+ ```
1480
+
1363
1481
  ### exclude-patterns
1364
1482
 
1365
1483
  to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash '\.iso$'` or the volflag `:c,nohash=\.iso$`, this has the following consequences:
@@ -1369,12 +1487,24 @@ to save some time, you can provide a regex pattern for filepaths to only index
1369
1487
 
1370
1488
  similarly, you can fully ignore files/folders using `--no-idx [...]` and `:c,noidx=\.iso$`
1371
1489
 
1490
+ NOTE: `no-idx` and/or `no-hash` prevents deduplication of those files
1491
+
1372
1492
  * when running on macos, all the usual apple metadata files are excluded by default
1373
1493
 
1374
1494
  if you set `--no-hash [...]` globally, you can enable hashing for specific volumes using flag `:c,nohash=`
1375
1495
 
1376
1496
  to exclude certain filepaths from search-results, use `--srch-excl` or volflag `srch_excl` instead of `--no-idx`, for example `--srch-excl 'password|logs/[0-9]'`
1377
1497
 
1498
+ config file example:
1499
+
1500
+ ```yaml
1501
+ [/games]
1502
+ /mnt/nas/games
1503
+ flags:
1504
+ noidx: \.iso$ # skip indexing iso-files
1505
+ srch_excl: password|logs/[0-9] # filter search results
1506
+ ```
1507
+
1378
1508
  ### filesystem guards
1379
1509
 
1380
1510
  avoid traversing into other filesystems using `--xdev` / volflag `:c,xdev`, skipping any symlinks or bind-mounts to another HDD for example
@@ -1395,6 +1525,20 @@ argument `--re-maxage 60` will rescan all volumes every 60 sec, same as volflag
1395
1525
 
1396
1526
  uploads are disabled while a rescan is happening, so rescans will be delayed by `--db-act` (default 10 sec) when there is write-activity going on (uploads, renames, ...)
1397
1527
 
1528
+ note: folder-thumbnails are selected during filesystem indexing, so periodic rescans can be used to keep them accurate as images are uploaded/deleted (or manually do a rescan with the `reload` button in the controlpanel)
1529
+
1530
+ config file example:
1531
+
1532
+ ```yaml
1533
+ [global]
1534
+ re-maxage: 3600
1535
+
1536
+ [/pics]
1537
+ /mnt/nas/pics
1538
+ flags:
1539
+ scan: 900
1540
+ ```
1541
+
1398
1542
 
1399
1543
  ## upload rules
1400
1544
 
@@ -1420,6 +1564,26 @@ you can also set transaction limits which apply per-IP and per-volume, but these
1420
1564
  notes:
1421
1565
  * `vmaxb` and `vmaxn` requires either the `e2ds` volflag or `-e2dsa` global-option
1422
1566
 
1567
+ config file example:
1568
+
1569
+ ```yaml
1570
+ [/inc]
1571
+ /mnt/nas/uploads
1572
+ accs:
1573
+ w: * # anyone can upload here
1574
+ rw: ed # only user "ed" can read-write
1575
+ flags:
1576
+ e2ds: # filesystem indexing is required for many of these:
1577
+ sz: 1k-3m # accept upload only if filesize in this range
1578
+ df: 4g # free disk space cannot go lower than this
1579
+ vmaxb: 1g # volume can never exceed 1 GiB
1580
+ vmaxn: 4k # ...or 4000 files, whichever comes first
1581
+ nosub # must upload to toplevel folder
1582
+ lifetime: 300 # uploads are deleted after 5min
1583
+ maxn: 250,3600 # each IP can upload 250 files in 1 hour
1584
+ maxb: 1g,300 # each IP can upload 1 GiB over 5 minutes
1585
+ ```
1586
+
1423
1587
 
1424
1588
  ## compress uploads
1425
1589
 
@@ -1465,10 +1629,24 @@ this can instead be kept in a single place using the `--hist` argument, or the `
1465
1629
  * `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
1466
1630
 
1467
1631
  note:
1632
+ * putting the hist-folders on an SSD is strongly recommended for performance
1468
1633
  * markdown edits are always stored in a local `.hist` subdirectory
1469
1634
  * on windows the volflag path is cyglike, so `/c/temp` means `C:\temp` but use regular paths for `--hist`
1470
1635
  * you can use cygpaths for volumes too, `-v C:\Users::r` and `-v /c/users::r` both work
1471
1636
 
1637
+ config file example:
1638
+
1639
+ ```yaml
1640
+ [global]
1641
+ hist: ~/.cache/copyparty # put db/thumbs/etc. here by default
1642
+
1643
+ [/pics]
1644
+ /mnt/nas/pics
1645
+ flags:
1646
+ hist: - # restore the default (/mnt/nas/pics/.hist/)
1647
+ hist: /mnt/nas/cache/pics/ # can be absolute path
1648
+ ```
1649
+
1472
1650
 
1473
1651
  ## metadata from audio files
1474
1652
 
@@ -1520,6 +1698,18 @@ copyparty can invoke external programs to collect additional metadata for files
1520
1698
 
1521
1699
  if something doesn't work, try `--mtag-v` for verbose error messages
1522
1700
 
1701
+ config file example; note that `mtp` is an additive option so all of the mtp options will take effect:
1702
+
1703
+ ```yaml
1704
+ [/music]
1705
+ /mnt/nas/music
1706
+ flags:
1707
+ mtp: .bpm=~/bin/audio-bpm.py # assign ".bpm" (numeric) with script
1708
+ mtp: key=f,t5,~/bin/audio-key.py # force/overwrite, 5sec timeout
1709
+ mtp: ext=an,~/bin/file-ext.py # will only run on non-audio files
1710
+ mtp: arch,built,ver,orig=an,eexe,edll,~/bin/exe.py # only exe/dll
1711
+ ```
1712
+
1523
1713
 
1524
1714
  ## event hooks
1525
1715
 
@@ -1548,13 +1738,35 @@ the PUSH and REQ examples have `t3` (timeout after 3 seconds) because they block
1548
1738
 
1549
1739
  see [zmq-recv.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/zmq-recv.py) if you need something to receive the messages with
1550
1740
 
1741
+ config file example; note that the hooks are additive options, so all of the xau options will take effect:
1742
+
1743
+ ```yaml
1744
+ [global]
1745
+ xau: zmq:pub:tcp://*:5556` # send a PUB to any/all connected SUB clients
1746
+ xau: t3,zmq:push:tcp://*:5557` # send PUSH to exactly one connected PULL cli
1747
+ xau: t3,j,zmq:req:tcp://localhost:5555` # send REQ to the connected REP cli
1748
+ ```
1749
+
1551
1750
 
1552
1751
  ### upload events
1553
1752
 
1554
1753
  the older, more powerful approach ([examples](./bin/mtag/)):
1555
1754
 
1556
1755
  ```
1557
- -v /mnt/inc:inc:w:c,mte=+x1:c,mtp=x1=ad,kn,/usr/bin/notify-send
1756
+ -v /mnt/inc:inc:w:c,e2d,e2t,mte=+x1:c,mtp=x1=ad,kn,/usr/bin/notify-send
1757
+ ```
1758
+
1759
+ that was the commandline example; here's the config file example:
1760
+
1761
+ ```yaml
1762
+ [/inc]
1763
+ /mnt/inc
1764
+ accs:
1765
+ w: *
1766
+ flags:
1767
+ e2d, e2t # enable indexing of uploaded files and their tags
1768
+ mte: +x1
1769
+ mtp: x1=ad,kn,/usr/bin/notify-send
1558
1770
  ```
1559
1771
 
1560
1772
  so filesystem location `/mnt/inc` shared at `/inc`, write-only for everyone, appending `x1` to the list of tags to index (`mte`), and using `/usr/bin/notify-send` to "provide" tag `x1` for any filetype (`ad`) with kill-on-timeout disabled (`kn`)
@@ -1568,6 +1780,8 @@ note that this is way more complicated than the new [event hooks](#event-hooks)
1568
1780
 
1569
1781
  note that it will occupy the parsing threads, so fork anything expensive (or set `kn` to have copyparty fork it for you) -- otoh if you want to intentionally queue/singlethread you can combine it with `--mtag-mt 1`
1570
1782
 
1783
+ for reference, if you were to do this using event hooks instead, it would be like this: `-e2d --xau notify-send,hello,--`
1784
+
1571
1785
 
1572
1786
  ## handlers
1573
1787
 
@@ -1575,6 +1789,8 @@ redefine behavior with plugins ([examples](./bin/handlers/))
1575
1789
 
1576
1790
  replace 404 and 403 errors with something completely different (that's it for now)
1577
1791
 
1792
+ as for client-side stuff, there is [plugins for modifying UI/UX](./contrib/plugins/)
1793
+
1578
1794
 
1579
1795
  ## ip auth
1580
1796
 
@@ -1636,6 +1852,8 @@ connecting to an aws s3 bucket and similar
1636
1852
 
1637
1853
  there is no built-in support for this, but you can use FUSE-software such as [rclone](https://rclone.org/) / [geesefs](https://github.com/yandex-cloud/geesefs) / [JuiceFS](https://juicefs.com/en/) to first mount your cloud storage as a local disk, and then let copyparty use (a folder in) that disk as a volume
1638
1854
 
1855
+ if copyparty is unable to access the local folder that rclone/geesefs/JuiceFS provides (for example if it looks invisible) then you may need to run rclone with `--allow-other` and/or enable `user_allow_other` in `/etc/fuse.conf`
1856
+
1639
1857
  you will probably get decent speeds with the default config, however most likely restricted to using one TCP connection per file, so the upload-client won't be able to send multiple chunks in parallel
1640
1858
 
1641
1859
  > before [v1.13.5](https://github.com/9001/copyparty/releases/tag/v1.13.5) it was recommended to use the volflag `sparse` to force-allow multiple chunks in parallel; this would improve the upload-speed from `1.5 MiB/s` to over `80 MiB/s` at the risk of provoking latent bugs in S3 or JuiceFS. But v1.13.5 added chunk-stitching, so this is now probably much less important. On the contrary, `nosparse` *may* now increase performance in some cases. Please try all three options (default, `sparse`, `nosparse`) as the optimal choice depends on your network conditions and software stack (both the FUSE-driver and cloud-server)
@@ -1896,7 +2114,7 @@ change the association of a file extension
1896
2114
 
1897
2115
  using commandline args, you can do something like `--mime gif=image/jif` and `--mime ts=text/x.typescript` (can be specified multiple times)
1898
2116
 
1899
- in a config-file, this is the same as:
2117
+ in a config file, this is the same as:
1900
2118
 
1901
2119
  ```yaml
1902
2120
  [global]
@@ -2156,6 +2374,8 @@ NOTE: curl will not send the original filename if you use `-T` combined with url
2156
2374
 
2157
2375
  sync folders to/from copyparty
2158
2376
 
2377
+ NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/server/latest/user_manual/sv/files/desktop_mobile_sync.html) and [syncthing](https://syncthing.net/) does, will never be supported! Only single-direction sync (server-to-client, or client-to-server) is possible with copyparty
2378
+
2159
2379
  the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
2160
2380
 
2161
2381
  alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
@@ -1,37 +1,37 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=5igJxZ_H08z51qaF_YrWsRp1SNzV810RHvVq38lHrtI,115336
3
- copyparty/__version__.py,sha256=3EfC4asnk6XDUfAe5nBClbNInAuJvogNSfslz0AxznQ,252
4
- copyparty/authsrv.py,sha256=uADnPkHiTH0UcBUr-w5hdUare6p-74O7cJQaL9lkpws,104513
2
+ copyparty/__main__.py,sha256=Tcvi7uIGA37yGVXJbbhn-Cr0OBtt81aRF2Or0ZHXz6U,116449
3
+ copyparty/__version__.py,sha256=Kh8nmhFwgLz9KpLvVPB-drOjIKb4uSXTYfW7c0xGr3E,252
4
+ copyparty/authsrv.py,sha256=-oy1PtcMONLcrMllSY2aPuyHWQmE0Ot_EQTX7UJ5tuc,107035
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=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=5QJPyuCUoQn2svd-4zzFmC0As13b08qenNJhPF5YkOU,10638
10
+ copyparty/cfg.py,sha256=-Cbva1shfXQVGFl8Xbo-bkUYjkpU6amPZpTyPJcE7M0,12926
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=IVOFG8zBQPMQDDv7RIStSJHwHiAnVNROZS37O5k465A,4524
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=yXpUvHcU3AKwQLEyvC-p-8mWdcuDJSueWg7J2gHzMCs,216276
14
+ copyparty/httpcli.py,sha256=Dr776ra7_wTCghymLOymy_Bpv8A15Qmgt9ERe-8kZ5A,219131
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
18
18
  copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
19
- copyparty/metrics.py,sha256=EOIiPOItEQmdK9YgNb75l0kCzanWb6RtJGwMI7ufifY,8966
19
+ copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
20
  copyparty/mtag.py,sha256=wWWc3BHMRP0u85KvdZpX3Dp4djDgAbXVrL9Pvab4nPQ,19925
21
- copyparty/multicast.py,sha256=Ha27l2oATEa-Qo2WOzkeRgjAm6G_YDCfbVJWR-ao2UE,12319
21
+ copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
22
22
  copyparty/pwhash.py,sha256=X87RWeay8IhzGVZLDKE5LctF9YVUcYxPAJ1BX6r_9CU,4248
23
23
  copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
27
- copyparty/svchub.py,sha256=mgZ2UndrMqWuUywecsCgVz488WoS0EFHR94VvWZjeT0,41454
27
+ copyparty/svchub.py,sha256=gBp7x1hGF4b6_nanW5QUQcz8UmMCad6DzdE2KD5cLvE,41462
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
29
  copyparty/tcpsrv.py,sha256=2q18dGR8jnezA4SMfUXa-wrGRGX3nHIwkxkWvkTzF2A,19889
30
30
  copyparty/tftpd.py,sha256=PXgG4rTmiaU_TavSyZWD5cFphdfChs9YvNY21qfExt8,13611
31
31
  copyparty/th_cli.py,sha256=PxDAmUvO_8Vm5edXiWtsCft0Fw69QL9rCHf9zLmUNeA,4800
32
32
  copyparty/th_srv.py,sha256=tHbh_Ve3v8tYclWH2thLs5oFufeXgJi1duUMveKIx9k,30725
33
33
  copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
- copyparty/up2k.py,sha256=alObSL1EVyPedi_24s7DBFlBzA0ZMkr9_jOtzdRV3vk,175427
34
+ copyparty/up2k.py,sha256=yli2ALT61o1sPta4ckJu4v7hwUrV7IAJSsJo1-OEZWY,175423
35
35
  copyparty/util.py,sha256=Y_znSn3hBNYaaduwcCB7mmBYsi6vv9CYC1zJ9rq9yeQ,99435
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
@@ -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=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
58
- copyparty/web/browser.css.gz,sha256=fs6dyfIin4XNWlw3d6dOInZaW0U33-hZKMwRCB3ge-Y,11645
59
- copyparty/web/browser.html,sha256=dekrZQ6w8ciB-QPlp-mjcuzUVKlsCYcvvi6efmXRfQE,4822
60
- copyparty/web/browser.js.gz,sha256=CKdT13IN5XDOExgTnCFYMgxP2Fr9mdjRFIQv1OZQkEM,91199
57
+ copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
+ copyparty/web/browser.css.gz,sha256=A44DddZBf-PEEMOj-u5YF_JrSk5DZYf-8f_J5jqC2is,11651
59
+ copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
+ copyparty/web/browser.js.gz,sha256=Ec5tzZutoI8daxzHdfRfDP7L9ovDTY10j2g-hkeRlmA,91598
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
@@ -84,7 +84,7 @@ copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
84
84
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
85
  copyparty/web/ui.css.gz,sha256=0sHIwGsL3_xH8Uu6N0Ag3bKBTjf-e_yfFbKynEZXAnk,2800
86
86
  copyparty/web/up2k.js.gz,sha256=N4idIqOefXurVh4ZhN6lv9nFPX_azLwE41vhXT2qBkI,23833
87
- copyparty/web/util.js.gz,sha256=amPEbzFzNKmkM40jeGYB3h7wpgpHNSxHX19vcNAH8MI,15087
87
+ copyparty/web/util.js.gz,sha256=wD3tP5j1iE5Uj5AvLW5zZbQJXDIFDlqgBTGdXeRVqo0,15110
88
88
  copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
@@ -100,7 +100,7 @@ copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ
100
100
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
101
101
  copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
102
102
  copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
103
- copyparty/web/deps/marked.js.gz,sha256=nay9sv1hlF9Y6ngXuXvE_O-MaTa7vE5OyK05-jjWx7M,22617
103
+ copyparty/web/deps/marked.js.gz,sha256=M6FwmQujGDX33fpo32JZRbxqtSdPzmh7WcaHK6SpZlc,22650
104
104
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
105
105
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
106
106
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.16.11.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.11.dist-info/METADATA,sha256=Rm11Nqji9bckgX12wuhgCBYYbYRzX_RKei8H1GIW17A,146927
114
- copyparty-1.16.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
- copyparty-1.16.11.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.11.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.11.dist-info/RECORD,,
112
+ copyparty-1.16.13.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.13.dist-info/METADATA,sha256=hm4IR2Xqc3bnZLZuEE3MPuNqwi73-IXzd_WqnVo_2NI,154166
114
+ copyparty-1.16.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.13.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.13.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.13.dist-info/RECORD,,