copyparty 1.16.12__py3-none-any.whl → 1.16.14__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
@@ -1031,6 +1031,7 @@ def add_upload(ap):
1031
1031
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
1032
1032
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
1033
1033
  ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1034
+ ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
1034
1035
  ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
1035
1036
  ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
1036
1037
 
@@ -1175,6 +1176,7 @@ def add_webdav(ap):
1175
1176
  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
1177
  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
1178
  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)")
1179
+ 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
1180
 
1179
1181
 
1180
1182
  def add_tftp(ap):
@@ -1260,7 +1262,7 @@ def add_optouts(ap):
1260
1262
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1261
1263
  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")
1262
1264
  ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
1263
- ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
1265
+ ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader-IP into the database; will also disable unpost, you may want \033[32m--forget-ip\033[0m instead (volflag=no_db_ip)")
1264
1266
 
1265
1267
 
1266
1268
  def add_safety(ap):
@@ -1387,7 +1389,7 @@ def add_transcoding(ap):
1387
1389
 
1388
1390
  def add_rss(ap):
1389
1391
  ap2 = ap.add_argument_group('RSS options')
1390
- ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental)")
1392
+ ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
1391
1393
  ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
1392
1394
  ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
1393
1395
  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")
@@ -1410,6 +1412,7 @@ def add_db_general(ap, hcores):
1410
1412
  ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
1411
1413
  ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
1412
1414
  ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
1415
+ ap2.add_argument("--forget-ip", metavar="MIN", type=int, default=0, help="remove uploader-IP from database (and make unpost impossible) \033[33mMIN\033[0m minutes after upload, for GDPR reasons. Default [\033[32m0\033[0m] is never-forget. [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month. (volflag=forget_ip)")
1413
1416
  ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
1414
1417
  ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink)")
1415
1418
  ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
@@ -1478,7 +1481,9 @@ def add_ui(ap, retry):
1478
1481
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1479
1482
  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)")
1480
1483
  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")
1484
+ 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)")
1481
1485
  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])")
1486
+ 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]")
1482
1487
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1483
1488
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
1484
1489
  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, 12)
3
+ VERSION = (1, 16, 14)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 2, 9)
5
+ BUILD_DT = (2025, 2, 19)
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,42 @@ 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
+ stripped = name.lstrip("-")
1386
+ zi = len(name) - len(stripped)
1387
+ if zi > 1:
1388
+ t = "WARNING: the config for volume [/%s] specified a volflag with multiple leading hyphens (%s); use one hyphen to remove, or zero hyphens to add a flag. Will now enable flag [%s]"
1389
+ self.log(t % (vpath, name, stripped), 3)
1390
+ name = stripped
1391
+ zi = 0
1392
+
1393
+ if stripped not in flagdescs and "-" in stripped:
1394
+ name = ("-" * zi) + stripped.replace("-", "_")
1395
+
1380
1396
  desc = flagdescs.get(name.lstrip("-"), "?").replace("\n", " ")
1381
1397
 
1398
+ if not name:
1399
+ self._e("└─unreadable-line")
1400
+ t = "WARNING: the config for volume [/%s] indicated that a volflag was to be defined, but the volflag name was blank"
1401
+ self.log(t % (vpath,), 3)
1402
+ return
1403
+
1382
1404
  if re.match("^-[^-]+$", name):
1383
1405
  t = "└─unset volflag [{}] ({})"
1384
1406
  self._e(t.format(name[1:], desc))
1385
1407
  flags[name] = True
1386
1408
  return
1387
1409
 
1388
- zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1410
+ zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1389
1411
  if name not in zs.split():
1390
1412
  if value is True:
1391
1413
  t = "└─add volflag [{}] = {} ({})"
@@ -1550,6 +1572,22 @@ class AuthSrv(object):
1550
1572
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1551
1573
  vol.root = vfs
1552
1574
 
1575
+ zs = "neversymlink"
1576
+ k_ign = set(zs.split())
1577
+ for vol in vfs.all_vols.values():
1578
+ unknown_flags = set()
1579
+ for k, v in vol.flags.items():
1580
+ stripped = k.lstrip("-")
1581
+ if k != stripped and stripped not in vol.flags:
1582
+ t = "WARNING: the config for volume [/%s] tried to remove volflag [%s] by specifying [%s] but that volflag was not already set"
1583
+ self.log(t % (vol.vpath, stripped, k), 3)
1584
+ k = stripped
1585
+ if k not in flagdescs and k not in k_ign:
1586
+ unknown_flags.add(k)
1587
+ if unknown_flags:
1588
+ t = "WARNING: the config for volume [/%s] has unrecognized volflags; will ignore: '%s'"
1589
+ self.log(t % (vol.vpath, "', '".join(unknown_flags)), 3)
1590
+
1553
1591
  enshare = self.args.shr
1554
1592
  shr = enshare[1:-1]
1555
1593
  shrs = enshare[1:]
@@ -1911,11 +1949,8 @@ class AuthSrv(object):
1911
1949
  if vf not in vol.flags:
1912
1950
  vol.flags[vf] = getattr(self.args, ga)
1913
1951
 
1914
- for k in ("nrand",):
1915
- if k not in vol.flags:
1916
- vol.flags[k] = getattr(self.args, k)
1917
-
1918
- for k in ("nrand", "u2abort", "ups_who", "zip_who"):
1952
+ zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
1953
+ for k in zs.split():
1919
1954
  if k in vol.flags:
1920
1955
  vol.flags[k] = int(vol.flags[k])
1921
1956
 
@@ -1967,8 +2002,10 @@ class AuthSrv(object):
1967
2002
 
1968
2003
  # append additive args from argv to volflags
1969
2004
  hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
1970
- for name in "mtp on404 on403".split() + hooks:
1971
- self._read_volflag(vol.flags, name, getattr(self.args, name), True)
2005
+ for name in "ext_th mtp on404 on403".split() + hooks:
2006
+ self._read_volflag(
2007
+ vol.vpath, vol.flags, name, getattr(self.args, name), True
2008
+ )
1972
2009
 
1973
2010
  for hn in hooks:
1974
2011
  cmds = vol.flags.get(hn)
@@ -1996,6 +2033,16 @@ class AuthSrv(object):
1996
2033
  ncmds.append(ocmd)
1997
2034
  vol.flags[hn] = ncmds
1998
2035
 
2036
+ ext_th = vol.flags["ext_th_d"] = {}
2037
+ etv = "(?)"
2038
+ try:
2039
+ for etv in vol.flags.get("ext_th") or []:
2040
+ k, v = etv.split("=")
2041
+ ext_th[k] = v
2042
+ except:
2043
+ t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
2044
+ self.log(t % (vol.vpath, etv), 3)
2045
+
1999
2046
  # d2d drops all database features for a volume
2000
2047
  for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
2001
2048
  if not vol.flags.get(grp, False):
@@ -2347,6 +2394,7 @@ class AuthSrv(object):
2347
2394
  "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
2348
2395
  }
2349
2396
  js_htm = {
2397
+ "SPINNER": self.args.spinner,
2350
2398
  "s_name": self.args.bname,
2351
2399
  "have_up2k_idx": "e2d" in vf,
2352
2400
  "have_acode": not self.args.no_acode,
@@ -2356,6 +2404,7 @@ class AuthSrv(object):
2356
2404
  "have_del": not self.args.no_del,
2357
2405
  "have_unpost": int(self.args.unpost),
2358
2406
  "have_emp": self.args.emp,
2407
+ "ext_th": vf.get("ext_th_d") or {},
2359
2408
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2360
2409
  "sba_md": vf.get("md_sba") or "",
2361
2410
  "sba_lg": vf.get("lg_sba") or "",
@@ -2376,6 +2425,7 @@ class AuthSrv(object):
2376
2425
  "u2j": self.args.u2j,
2377
2426
  "u2sz": self.args.u2sz,
2378
2427
  "u2ts": vf["u2ts"],
2428
+ "u2ow": vf["u2ow"],
2379
2429
  "frand": bool(vf.get("rand")),
2380
2430
  "lifetime": vn.js_ls["lifetime"],
2381
2431
  "u2sort": self.args.u2sort,
@@ -2759,7 +2809,9 @@ class AuthSrv(object):
2759
2809
  zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
2760
2810
  lst = set(zs.split())
2761
2811
  askip = set("a v c vc cgen exp_lg exp_md theme".split())
2762
- fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
2812
+
2813
+ 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"
2814
+ fskip = set(t.split())
2763
2815
 
2764
2816
  # keymap from argv to vflag
2765
2817
  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"""
@@ -40,6 +43,7 @@ def vf_bmap() :
40
43
  "gsel",
41
44
  "hardlink",
42
45
  "magic",
46
+ "no_db_ip",
43
47
  "no_sb_md",
44
48
  "no_sb_lg",
45
49
  "nsort",
@@ -70,6 +74,7 @@ def vf_vmap() :
70
74
  }
71
75
  for k in (
72
76
  "dbd",
77
+ "forget_ip",
73
78
  "hsortn",
74
79
  "html_head",
75
80
  "lg_sbf",
@@ -77,6 +82,7 @@ def vf_vmap() :
77
82
  "lg_sba",
78
83
  "md_sba",
79
84
  "nrand",
85
+ "u2ow",
80
86
  "og_desc",
81
87
  "og_site",
82
88
  "og_th",
@@ -106,6 +112,7 @@ def vf_cmap() :
106
112
  for k in (
107
113
  "exp_lg",
108
114
  "exp_md",
115
+ "ext_th",
109
116
  "mte",
110
117
  "mth",
111
118
  "mtp",
@@ -152,7 +159,8 @@ flagcats = {
152
159
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
153
160
  "nosub": "forces all uploads into the top folder of the vfs",
154
161
  "magic": "enables filetype detection for nameless uploads",
155
- "gz": "allows server-side gzip of uploads with ?gz (also c,xz)",
162
+ "gz": "allows server-side gzip compression of uploads with ?gz",
163
+ "xz": "allows server-side lzma compression of uploads with ?xz",
156
164
  "pk": "forces server-side compression, optional arg: xz,9",
157
165
  },
158
166
  "upload rules": {
@@ -163,6 +171,7 @@ flagcats = {
163
171
  "medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
164
172
  "rand": "force randomized filenames, 9 chars long by default",
165
173
  "nrand=N": "randomized filenames are N chars long",
174
+ "u2ow=N": "overwrite existing files? 0=no 1=if-older 2=always",
166
175
  "u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
167
176
  "u2abort=1": "allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk",
168
177
  "sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
@@ -179,8 +188,11 @@ flagcats = {
179
188
  "e2dsa": "scans all folders for new files on startup; also sets -e2d",
180
189
  "e2t": "enable multimedia indexing; makes it possible to search for tags",
181
190
  "e2ts": "scan existing files for tags on startup; also sets -e2t",
182
- "e2tsa": "delete all metadata from DB (full rescan); also sets -e2ts",
191
+ "e2tsr": "delete all metadata from DB (full rescan); also sets -e2ts",
183
192
  "d2ts": "disables metadata collection for existing files",
193
+ "e2v": "verify integrity on startup by hashing files and comparing to db",
194
+ "e2vu": "when e2v fails, update the db (assume on-disk files are good)",
195
+ "e2vp": "when e2v fails, panic and quit copyparty",
184
196
  "d2ds": "disables onboot indexing, overrides -e2ds*",
185
197
  "d2t": "disables metadata collection, overrides -e2t*",
186
198
  "d2v": "disables file verification, overrides -e2v*",
@@ -190,6 +202,8 @@ flagcats = {
190
202
  "nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
191
203
  "noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
192
204
  "noforget": "don't forget files when deleted from disk",
205
+ "forget_ip=43200": "forget uploader-IP after 30 days (GDPR)",
206
+ "no_db_ip": "never store uploader-IP in the db; disables unpost",
193
207
  "fat32": "avoid excessive reindexing on android sdcardfs",
194
208
  "dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
195
209
  "xlink": "cross-volume dupe detection / linking (dangerous)",
@@ -200,6 +214,8 @@ flagcats = {
200
214
  "srch_excl": "exclude search results with URL matching this regex",
201
215
  },
202
216
  'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
217
+ "mte=artist,title": "media-tags to index/display",
218
+ "mth=fmt,res,ac": "media-tags to hide by default",
203
219
  "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
204
220
  "mtp=ahash,vhash=media-hash.py": "collects two tags at once",
205
221
  },
@@ -213,6 +229,7 @@ flagcats = {
213
229
  "crop": "center-cropping (y/n/fy/fn)",
214
230
  "th3x": "3x resolution (y/n/fy/fn)",
215
231
  "convt": "conversion timeout in seconds",
232
+ "ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
216
233
  },
217
234
  "handlers\n(better explained in --help-handlers)": {
218
235
  "on404=PY": "handle 404s by executing PY file",
@@ -235,8 +252,12 @@ flagcats = {
235
252
  "grid": "show grid/thumbnails by default",
236
253
  "gsel": "select files in grid by ctrl-click",
237
254
  "sort": "default sort order",
255
+ "nsort": "natural-sort of leading digits in filenames",
256
+ "hsortn": "number of sort-rules to add to media URLs",
238
257
  "unlist": "dont list files matching REGEX",
239
258
  "html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
259
+ "tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
260
+ "nodirsz": "don't show total folder size",
240
261
  "robots": "allows indexing by search engines (default)",
241
262
  "norobots": "kindly asks search engines to leave",
242
263
  "no_sb_md": "disable js sandbox for markdown files",
@@ -249,12 +270,33 @@ flagcats = {
249
270
  "lg_sba": "value of iframe allow-prop for *logue-sandbox",
250
271
  "nohtml": "return html and markdown as text/html",
251
272
  },
273
+ "opengraph (discord embeds)": {
274
+ "og": "enable OG (disables hotlinking)",
275
+ "og_site": "sitename; defaults to --name, disable with '-'",
276
+ "og_desc": "description text for all files; disable with '-'",
277
+ "og_th=jf": "thumbnail format; j / jf / jf3 / w / w3 / ...",
278
+ "og_title_a": "audio title format; default: {{ artist }} - {{ title }}",
279
+ "og_title_v": "video title format; default: {{ title }}",
280
+ "og_title_i": "image title format; default: {{ title }}",
281
+ "og_title=foo": "fallback title if there's nothing in the db",
282
+ "og_s_title": "force default title; do not read from tags",
283
+ "og_tpl": "custom html; see --og-tpl in --help",
284
+ "og_no_head": "you want to add tags manually with og_tpl",
285
+ "og_ua": "if defined: only send OG html if useragent matches this regex",
286
+ },
287
+ "textfiles": {
288
+ "exp": "enable textfile expansion; see --help-exp",
289
+ "exp_md": "placeholders to expand in markdown files; see --help",
290
+ "exp_lg": "placeholders to expand in prologue/epilogue; see --help",
291
+ },
252
292
  "others": {
253
293
  "dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
254
294
  "fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
255
295
  "fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
296
+ "rss": "allow '?rss' URL suffix (experimental)",
256
297
  "ups_who=2": "restrict viewing the list of recent uploads",
257
298
  "zip_who=2": "restrict access to download-as-zip/tar",
299
+ "nopipe": "disable race-the-beam (download unfinished uploads)",
258
300
  "mv_retry": "ms-windows: timeout for renaming busy files",
259
301
  "rm_retry": "ms-windows: timeout for deleting busy files",
260
302
  "davauth": "ask webdav clients to login for all folders",
@@ -264,3 +306,4 @@ flagcats = {
264
306
 
265
307
 
266
308
  flagdescs = {k.split("=")[0]: v for tab in flagcats.values() for k, v in tab.items()}
309
+
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):
@@ -1768,6 +1768,12 @@ class HttpCli(object):
1768
1768
  if "%" in self.req:
1769
1769
  self.log(" `-- %r" % (self.vpath,))
1770
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
+
1771
1777
  try:
1772
1778
  return self._mkdir(self.vpath, True)
1773
1779
  except Pebkac as ex:
@@ -1777,14 +1783,36 @@ class HttpCli(object):
1777
1783
  self.reply(b"", ex.code)
1778
1784
  return True
1779
1785
 
1780
- def handle_move(self) :
1786
+ def handle_cpmv(self) :
1781
1787
  dst = self.headers["destination"]
1782
- 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
+
1783
1803
  dst = unquotep(dst)
1784
- if not self._mv(self.vpath, dst.lstrip("/")):
1785
- return False
1786
1804
 
1787
- return True
1805
+ # overwrite=True is default; rfc4918 9.8.4
1806
+ zs = self.headers.get("overwrite", "").lower()
1807
+ overwrite = zs not in ["f", "false"]
1808
+
1809
+ try:
1810
+ fun = self._cp if self.mode == "COPY" else self._mv
1811
+ return fun(self.vpath, dst.lstrip("/"), overwrite)
1812
+ except Pebkac as ex:
1813
+ if ex.code == 403:
1814
+ ex.code = 401
1815
+ raise
1788
1816
 
1789
1817
  def _applesan(self) :
1790
1818
  if self.args.dav_mac or "Darwin/" not in self.ua:
@@ -4788,9 +4816,12 @@ class HttpCli(object):
4788
4816
  # that the client is not a graphical browser
4789
4817
  if (
4790
4818
  rc == 403
4791
- and not self.pw
4792
- and not self.ua.startswith("Mozilla/")
4819
+ and self.uname == "*"
4793
4820
  and "sec-fetch-site" not in self.headers
4821
+ and (
4822
+ not self.ua.startswith("Mozilla/")
4823
+ or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
4824
+ )
4794
4825
  ):
4795
4826
  rc = 401
4796
4827
  self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
@@ -5419,6 +5450,8 @@ class HttpCli(object):
5419
5450
 
5420
5451
  def handle_rm(self, req ) :
5421
5452
  if not req and not self.can_delete:
5453
+ if self.mode == "DELETE" and self.uname == "*":
5454
+ raise Pebkac(401, "authenticate") # webdav
5422
5455
  raise Pebkac(403, "'delete' not allowed for user " + self.uname)
5423
5456
 
5424
5457
  if self.args.no_del:
@@ -5453,14 +5486,22 @@ class HttpCli(object):
5453
5486
  if not dst:
5454
5487
  raise Pebkac(400, "need dst vpath")
5455
5488
 
5456
- return self._mv(self.vpath, dst.lstrip("/"))
5489
+ return self._mv(self.vpath, dst.lstrip("/"), False)
5457
5490
 
5458
- def _mv(self, vsrc , vdst ) :
5491
+ def _mv(self, vsrc , vdst , overwrite ) :
5459
5492
  if self.args.no_mv:
5460
5493
  raise Pebkac(403, "the rename/move feature is disabled in server config")
5461
5494
 
5462
- self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5463
- self.asrv.vfs.get(vdst, self.uname, False, True)
5495
+ # `handle_cpmv` will catch 403 from these and raise 401
5496
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5497
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5498
+
5499
+ if overwrite:
5500
+ dabs = dvn.canonical(drem)
5501
+ if bos.path.exists(dabs):
5502
+ self.log("overwriting %s" % (dabs,))
5503
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5504
+ wunlink(self.log, dabs, dvn.flags)
5464
5505
 
5465
5506
  x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
5466
5507
  self.loud_reply(x.get(), status=201)
@@ -5476,14 +5517,21 @@ class HttpCli(object):
5476
5517
  if not dst:
5477
5518
  raise Pebkac(400, "need dst vpath")
5478
5519
 
5479
- return self._cp(self.vpath, dst.lstrip("/"))
5520
+ return self._cp(self.vpath, dst.lstrip("/"), False)
5480
5521
 
5481
- def _cp(self, vsrc , vdst ) :
5522
+ def _cp(self, vsrc , vdst , overwrite ) :
5482
5523
  if self.args.no_cp:
5483
5524
  raise Pebkac(403, "the copy feature is disabled in server config")
5484
5525
 
5485
- self.asrv.vfs.get(vsrc, self.uname, True, False)
5486
- self.asrv.vfs.get(vdst, self.uname, False, True)
5526
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False)
5527
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5528
+
5529
+ if overwrite:
5530
+ dabs = dvn.canonical(drem)
5531
+ if bos.path.exists(dabs):
5532
+ self.log("overwriting %s" % (dabs,))
5533
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5534
+ wunlink(self.log, dabs, dvn.flags)
5487
5535
 
5488
5536
  x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
5489
5537
  self.loud_reply(x.get(), status=201)
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
@@ -553,6 +553,7 @@ class Up2k(object):
553
553
  else:
554
554
  # important; not deferred by db_act
555
555
  timeout = self._check_lifetimes()
556
+ timeout = min(self._check_forget_ip(), timeout)
556
557
  try:
557
558
  if self.args.shr:
558
559
  timeout = min(self._check_shares(), timeout)
@@ -613,6 +614,43 @@ class Up2k(object):
613
614
  for v in vols:
614
615
  volage[v] = now
615
616
 
617
+ def _check_forget_ip(self) :
618
+ now = time.time()
619
+ timeout = now + 9001
620
+ for vp, vol in sorted(self.vfs.all_vols.items()):
621
+ maxage = vol.flags["forget_ip"]
622
+ if not maxage:
623
+ continue
624
+
625
+ cur = self.cur.get(vol.realpath)
626
+ if not cur:
627
+ continue
628
+
629
+ cutoff = now - maxage * 60
630
+
631
+ for _ in range(2):
632
+ q = "select ip, at from up where ip > '' order by +at limit 1"
633
+ hits = cur.execute(q).fetchall()
634
+ if not hits:
635
+ break
636
+
637
+ remains = hits[0][1] - cutoff
638
+ if remains > 0:
639
+ timeout = min(timeout, now + remains)
640
+ break
641
+
642
+ q = "update up set ip = '' where ip > '' and at <= %d"
643
+ cur.execute(q % (cutoff,))
644
+ zi = cur.rowcount
645
+ cur.connection.commit()
646
+
647
+ t = "forget-ip(%d) removed %d IPs from db [/%s]"
648
+ self.log(t % (maxage, zi, vol.vpath))
649
+
650
+ timeout = min(timeout, now + 900)
651
+
652
+ return timeout
653
+
616
654
  def _check_lifetimes(self) :
617
655
  now = time.time()
618
656
  timeout = now + 9001
@@ -1074,7 +1112,7 @@ class Up2k(object):
1074
1112
  ft = "\033[0;32m{}{:.0}"
1075
1113
  ff = "\033[0;35m{}{:.0}"
1076
1114
  fv = "\033[0;36m{}:\033[90m{}"
1077
- zs = "html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
1115
+ zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
1078
1116
  fx = set(zs.split())
1079
1117
  fd = vf_bmap()
1080
1118
  fd.update(vf_cmap())
@@ -3319,7 +3357,17 @@ class Up2k(object):
3319
3357
  return fname
3320
3358
 
3321
3359
  fp = djoin(fdir, fname)
3322
- if job.get("replace") and bos.path.exists(fp):
3360
+
3361
+ ow = job.get("replace") and bos.path.exists(fp)
3362
+ if ow and "mt" in str(job["replace"]).lower():
3363
+ mts = bos.stat(fp).st_mtime
3364
+ mtc = job["lmod"]
3365
+ if mtc < mts:
3366
+ t = "will not overwrite; server %d sec newer than client; %d > %d %r"
3367
+ self.log(t % (mts - mtc, mts, mtc, fp))
3368
+ ow = False
3369
+
3370
+ if ow:
3323
3371
  self.log("replacing existing file at %r" % (fp,))
3324
3372
  cur = None
3325
3373
  ptop = job["ptop"]
@@ -3770,7 +3818,7 @@ class Up2k(object):
3770
3818
  db_ip = ""
3771
3819
  else:
3772
3820
  # plugins may expect this to look like an actual IP
3773
- db_ip = "1.1.1.1" if self.args.no_db_ip else ip
3821
+ db_ip = "1.1.1.1" if "no_db_ip" in vflags else ip
3774
3822
 
3775
3823
  sql = "insert into up values (?,?,?,?,?,?,?)"
3776
3824
  v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0))
@@ -4610,7 +4658,7 @@ class Up2k(object):
4610
4658
 
4611
4659
  cur = self.cur.get(ptop)
4612
4660
  if not cur:
4613
- return None, None, None, None, None, None
4661
+ return None, None, None, None, "", None
4614
4662
 
4615
4663
  rd, fn = vsplit(vrem)
4616
4664
  q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1"
@@ -4623,7 +4671,7 @@ class Up2k(object):
4623
4671
  if hit:
4624
4672
  wark, ftime, fsize, ip, at = hit
4625
4673
  return cur, wark, ftime, fsize, ip, at
4626
- return cur, None, None, None, None, None
4674
+ return cur, None, None, None, "", None
4627
4675
 
4628
4676
  def _forget_file(
4629
4677
  self,
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "2.9"
5
- S_BUILD_DT = "2025-01-27"
4
+ S_VERSION = "2.10"
5
+ S_BUILD_DT = "2025-02-19"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -805,7 +805,9 @@ def handshake(ar, file, search):
805
805
  else:
806
806
  if ar.touch:
807
807
  req["umod"] = True
808
- if ar.ow:
808
+ if ar.owo:
809
+ req["replace"] = "mt"
810
+ elif ar.ow:
809
811
  req["replace"] = True
810
812
 
811
813
  file.recheck = False
@@ -1536,6 +1538,7 @@ source file/folder selection uses rsync syntax, meaning that:
1536
1538
  ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
1537
1539
  ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
1538
1540
  ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
1541
+ ap.add_argument("--owo", action="store_true", help="overwrite existing files if server-file is older")
1539
1542
  ap.add_argument("--spd", action="store_true", help="print speeds for each file")
1540
1543
  ap.add_argument("--version", action="store_true", help="show version and exit")
1541
1544
 
Binary file
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.12
3
+ Version: 1.16.14
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
@@ -151,9 +151,11 @@ turn almost any device into a file server with resumable uploads/downloads using
151
151
  * [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
152
152
  * [real-ip](#real-ip) - teaching copyparty how to see client IPs
153
153
  * [reverse-proxy performance](#reverse-proxy-performance)
154
+ * [permanent cloudflare tunnel](#permanent-cloudflare-tunnel) - if you have a domain and want to get your copyparty online real quick
154
155
  * [prometheus](#prometheus) - metrics/stats can be enabled
155
156
  * [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
156
157
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
158
+ * [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
157
159
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
158
160
  * [packages](#packages) - the party might be closer than you think
159
161
  * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
@@ -216,8 +218,8 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
216
218
  * **MacOS:** `port install py-Pillow ffmpeg`
217
219
  * **MacOS** (alternative): `brew install pillow ffmpeg`
218
220
  * **Windows:** `python -m pip install --user -U Pillow`
219
- * install python and ffmpeg manually; do not use `winget` or `Microsoft Store` (it breaks $PATH)
220
- * copyparty.exe comes with `Pillow` and only needs `ffmpeg`
221
+ * install [python](https://www.python.org/downloads/windows/) and [ffmpeg](#optional-dependencies) manually; do not use `winget` or `Microsoft Store` (it breaks $PATH)
222
+ * copyparty.exe comes with `Pillow` and only needs [ffmpeg](#optional-dependencies) for mediatags/videothumbs
221
223
  * see [optional dependencies](#optional-dependencies) to enable even more features
222
224
 
223
225
  running copyparty without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; you may want [accounts and volumes](#accounts-and-volumes)
@@ -240,6 +242,8 @@ first download [cloudflared](https://developers.cloudflare.com/cloudflare-one/co
240
242
 
241
243
  as the tunnel starts, it will show a URL which you can share to let anyone browse your stash or upload files to you
242
244
 
245
+ but if you have a domain, then you probably want to skip the random autogenerated URL and instead make a [permanent cloudflare tunnel](#permanent-cloudflare-tunnel)
246
+
243
247
  since people will be connecting through cloudflare, run copyparty with `--xff-hdr cf-connecting-ip` to detect client IPs correctly
244
248
 
245
249
 
@@ -458,6 +462,9 @@ upgrade notes
458
462
 
459
463
  "frequently" asked questions
460
464
 
465
+ * can I change the 🌲 spinning pine-tree loading animation?
466
+ * [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
467
+
461
468
  * is it possible to block read-access to folders unless you know the exact URL for a particular file inside?
462
469
  * yes, using the [`g` permission](#accounts-and-volumes), see the examples there
463
470
  * you can also do this with linux filesystem permissions; `chmod 111 music` will make it possible to access files and folders inside the `music` folder but not list the immediate contents -- also works with other software, not just copyparty
@@ -480,6 +487,14 @@ upgrade notes
480
487
  * copyparty seems to think I am using http, even though the URL is https
481
488
  * your reverse-proxy is not sending the `X-Forwarded-Proto: https` header; this could be because your reverse-proxy itself is confused. Ensure that none of the intermediates (such as cloudflare) are terminating https before the traffic hits your entrypoint
482
489
 
490
+ * thumbnails are broken (you get a colorful square which says the filetype instead)
491
+ * you need to install `FFmpeg` or `Pillow`; see [thumbnails](#thumbnails)
492
+
493
+ * thumbnails are broken (some images appear, but other files just get a blank box, and/or the broken-image placeholder)
494
+ * probably due to a reverse-proxy messing with the request URLs and stripping the query parameters (`?th=w`), so check your URL rewrite rules
495
+ * could also be due to incorrect caching settings in reverse-proxies and/or CDNs, so make sure that nothing is set to ignore the query string
496
+ * could also be due to misbehaving privacy-related browser extensions, so try to disable those
497
+
483
498
  * i want to learn python and/or programming and am considering looking at the copyparty source code in that occasion
484
499
  * ```bash
485
500
  _| _ __ _ _|_
@@ -710,6 +725,7 @@ press `g` or `田` to toggle grid-view instead of the file listing and `t` togg
710
725
  it does static images with Pillow / pyvips / FFmpeg, and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
711
726
  * pyvips is 3x faster than Pillow, Pillow is 3x faster than FFmpeg
712
727
  * disable thumbnails for specific volumes with volflag `dthumb` for all, or `dvthumb` / `dathumb` / `dithumb` for video/audio/images only
728
+ * for installing FFmpeg on windows, see [optional dependencies](#optional-dependencies)
713
729
 
714
730
  audio files are converted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
715
731
 
@@ -721,6 +737,8 @@ enabling `multiselect` lets you click files to select them, and then shift-click
721
737
  * `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
722
738
  * the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
723
739
 
740
+ to show `/icons/exe.png` as the thumbnail for all .exe files, `--ext-th=exe=/icons/exe.png` (optionally as a volflag)
741
+
724
742
  config file example:
725
743
 
726
744
  ```yaml
@@ -735,6 +753,7 @@ config file example:
735
753
  flags:
736
754
  dthumb # disable ALL thumbnails and audio transcoding
737
755
  dvthumb # only disable video thumbnails
756
+ ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
738
757
  th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
739
758
  ```
740
759
 
@@ -818,8 +837,11 @@ the up2k UI is the epitome of polished intuitive experiences:
818
837
  * "parallel uploads" specifies how many chunks to upload at the same time
819
838
  * `[🏃]` analysis of other files should continue while one is uploading
820
839
  * `[🥔]` shows a simpler UI for faster uploads from slow devices
840
+ * `[🛡️]` decides when to overwrite existing files on the server
841
+ * `🛡️` = never (generate a new filename instead)
842
+ * `🕒` = overwrite if the server-file is older
843
+ * `♻️` = always overwrite if the files are different
821
844
  * `[🎲]` generate random filenames during upload
822
- * `[📅]` preserve last-modified timestamps; server times will match yours
823
845
  * `[🔎]` switch between upload and [file-search](#file-search) mode
824
846
  * ignore `[🔎]` if you add files by dragging them into the browser
825
847
 
@@ -2036,6 +2058,26 @@ in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds
2036
2058
  * if these results are bullshit because my config exampels are bad, please submit corrections!
2037
2059
 
2038
2060
 
2061
+ ## permanent cloudflare tunnel
2062
+
2063
+ if you have a domain and want to get your copyparty online real quick, either from your home-PC behind a CGNAT or from a server without an existing [reverse-proxy](#reverse-proxy) setup, one approach is to create a [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/) (formerly "Argo Tunnel")
2064
+
2065
+ I'd recommend making a `Locally-managed tunnel` for more control, but if you prefer to make a `Remotely-managed tunnel` then this is currently how:
2066
+
2067
+ * `cloudflare dashboard` » `zero trust` » `networks` » `tunnels` » `create a tunnel` » `cloudflared` » choose a cool `subdomain` and leave the `path` blank, and use `service type` = `http` and `URL` = `127.0.0.1:3923`
2068
+
2069
+ * and if you want to just run the tunnel without installing it, skip the `cloudflared service install BASE64` step and instead do `cloudflared --no-autoupdate tunnel run --token BASE64`
2070
+
2071
+ NOTE: since people will be connecting through cloudflare, as mentioned in [real-ip](#real-ip) you should run copyparty with `--xff-hdr cf-connecting-ip` to detect client IPs correctly
2072
+
2073
+ config file example:
2074
+
2075
+ ```yaml
2076
+ [global]
2077
+ xff-hdr: cf-connecting-ip
2078
+ ```
2079
+
2080
+
2039
2081
  ## prometheus
2040
2082
 
2041
2083
  metrics/stats can be enabled at URL `/.cpr/metrics` for grafana / prometheus / etc (openmetrics 1.0.0)
@@ -2122,6 +2164,18 @@ in a config file, this is the same as:
2122
2164
  run copyparty with `--mimes` to list all the default mappings
2123
2165
 
2124
2166
 
2167
+ ### GDPR compliance
2168
+
2169
+ imagine using copyparty professionally... **TINLA/IANAL; EU laws are hella confusing**
2170
+
2171
+ * remember to disable logging, or configure logrotation to an acceptable timeframe with `-lo cpp-%Y-%m%d.txt.xz` or similar
2172
+
2173
+ * if running with the database enabled (recommended), then have it forget uploader-IPs after some time using `--forget-ip 43200`
2174
+ * don't set it too low; [unposting](#unpost) a file is no longer possible after this takes effect
2175
+
2176
+ * if you actually *are* a lawyer then I'm open for feedback, would be fun
2177
+
2178
+
2125
2179
  ### feature chickenbits
2126
2180
 
2127
2181
  buggy feature? rip it out by setting any of the following environment variables to disable its associated bell or whistle,
@@ -2639,6 +2693,8 @@ enable [smb](#smb-server) support (**not** recommended): `impacket==0.12.0`
2639
2693
 
2640
2694
  `pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
2641
2695
 
2696
+ to install FFmpeg on Windows, grab [a recent build](https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z) -- you need `ffmpeg.exe` and `ffprobe.exe` from inside the `bin` folder; copy them into `C:\Windows\System32` or any other folder that's in your `%PATH%`
2697
+
2642
2698
 
2643
2699
  ### dependency chickenbits
2644
2700
 
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=ATbSyFWmocvOlarT6d-nglHBf3O4KmX4zrSwW-Bz1WQ,115851
3
- copyparty/__version__.py,sha256=hb9GoVB8qCxT8TBRr_QaRRAT3xQiXkZaGN_wbrwbd-w,251
4
- copyparty/authsrv.py,sha256=SJoNXQmE2yx-3DvLiHWCfrcW67uF2b9v3mzD0gFNaWY,105412
2
+ copyparty/__main__.py,sha256=JDjMsCiOMMBPs8HR8vpgEJMgnxq-qjX-hcgxKHF9WX4,117137
3
+ copyparty/__version__.py,sha256=-v6S2kv2wFcsrV1NBHpDXFZYQtr_j2DfwN5pjYFcYX4,252
4
+ copyparty/authsrv.py,sha256=EqlE2YztBcZHF5MHStZawA8ZKSlm-ePkrhsen90lKE8,107740
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=n6mZmlcvT7spUIyhyKfUfd3V6ZX8mX-8QpdeMvYqb9w,10788
10
+ copyparty/cfg.py,sha256=Z04AuNy-viZiiVJ8fLf3RULgH4w8ZidjsAVj4cmJKlA,13272
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=zIzrI9BRTDiBxj8ptDjPCzB3mK46JrRrbmz2K4dygJM,217236
14
+ copyparty/httpcli.py,sha256=QKgO3TTweln1N_w6Du7PDs1HJrT1c_zISUDZoima_X0,219162
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
@@ -24,14 +24,14 @@ 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=flkVZkHy7cfRZh7DSbTMbr4jfw7poZDOX3gFu5vyFxM,176973
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
@@ -55,9 +55,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
- copyparty/web/browser.css.gz,sha256=WwVIXAbrqU2TlNEoDg5D7XJU1o7iAogJoS8aCfSvuOo,11645
58
+ copyparty/web/browser.css.gz,sha256=_HiFW5vPUusWadoqdY8ZihuWizY9UECAc5nIamBPRi4,11654
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=XmTA7KN35cL_Av3nHJy6L8zkg2H307spGoagnBXKg3g,91462
60
+ copyparty/web/browser.js.gz,sha256=7XJ99kbDhfTelhK92K2MhJu1f47XSHhYtztjAhxPOk4,92085
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
@@ -83,12 +83,12 @@ copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,27
83
83
  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
- copyparty/web/up2k.js.gz,sha256=N4idIqOefXurVh4ZhN6lv9nFPX_azLwE41vhXT2qBkI,23833
86
+ copyparty/web/up2k.js.gz,sha256=o3FAMh8TxmN7SfiI15yQddyR1JSpzhHWC4u6qztNJgY,24057
87
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
91
- copyparty/web/a/u2c.py,sha256=8F-uVfu0ceXRehbNLjjRfcBKStP5Lm_vJ_JAhEM6zgk,52994
91
+ copyparty/web/a/u2c.py,sha256=oRKEApPuaXiOkmUyA-WGCDjtWsn8FirSW6nCJtx9sgk,53157
92
92
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
93
93
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
94
94
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -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.12.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.12.dist-info/METADATA,sha256=VzmaUTc62j97TcHYADjryHTMmKHBa1GhobpWJdtI9pk,153975
114
- copyparty-1.16.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
- copyparty-1.16.12.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.12.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.12.dist-info/RECORD,,
112
+ copyparty-1.16.14.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.14.dist-info/METADATA,sha256=MwVuGqaNzreAqN-HP6IfmsOzEKGS6_iYy2BB3dCvFbU,157768
114
+ copyparty-1.16.14.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.14.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.14.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.14.dist-info/RECORD,,