Compare commits

...

18 commits

Author SHA1 Message Date
ed fbcb773307 failed the git skillcheck 2026-08-06 22:10:18 +00:00
ed 3eb7b46d83 optimize 2026-08-06 21:08:34 +00:00
h3 4f20738b71 haproxy: add X-Forwarded-Proto to example (codeberg#6)
Reviewed-on: https://codeberg.org/9001/copyparty/pulls/6
2026-08-05 01:52:58 +02:00
ed ef75220faf update pkgs to 1.20.20 2026-08-03 22:36:36 +00:00
ed a3acbfcd5b v1.20.20 2026-08-03 22:32:26 +00:00
Danila Kamaev fdf52a8c4e
wopi: fix onlyoffice opening docs in readonly mode (#1585) 2026-08-03 22:02:18 +00:00
ed b9405a3e25 mute no-rss-warning for shares 2026-08-03 21:57:22 +00:00
ed 6f924829ee detect broken csp nonce 2026-08-03 21:56:45 +00:00
Danila Kamaev 24a4090fc7
wopi: add wopi-urls option (#1580);
map different copyparty hosts to different WOPI client addresses
2026-08-03 20:30:38 +00:00
ed c0c638c917 up2k: re-hs if unknown-wark or chunk-exists;
previous behavior:

* when a chunk already existed on the server, it would
   unstitch and continue retrying each individual chunk

* upon unknown-wark (file already completed), it would
   upload the remaining scheduled chunks before bailing

the unknown-wark behavior was accidental bullshit

the chunk-exists behavior was intentional, assumed optimal for
preserving tcp window-scaling, just not when running behind a
bufferbloating reverseproxy which may disconnect the client on
a timeout before the response is delivered, and "some clients"
still follow rfc2616-8.2.4 (retries the POST, wtf...)
wasting bandwidth + maybe further worsening conditions

"some clients" is specifically firefox with the devtools not open

another firefox joke is that it doesn't read the server-response
if the server does not drain the request-body; we end up in
xhr.onerror with no idea why, must assume the worst

new behavior:

in both scenarios, drop all scheduled chunks to be uploaded and
redo handshake, to as far as possible avoid retransmissions;
firefox will still eagerly retry 4 times but whatever

u2c was/is fine; nothing to be done

depending on network conditions and physical distance, this MAY
result in lower total speed, but the average case is likely a
net-positive, wasting less bandwidth on retransmissions,
in exchange for resetting tcp window-scaling
2026-08-03 20:08:19 +00:00
ed 2c6497b644 add hotkey F4 2026-08-03 18:55:24 +00:00
ed 2c4033831f js: while(apop) 2026-08-02 23:02:44 +00:00
ed 2880117265 fix ^a in search-results; closes #1581 2026-08-02 22:41:57 +00:00
yuki 63c61b6729
nixos: fix version checker (#1582)
Nixpkgs patches many libraries to read certificate bundle mentioned in
NIX_SSL_CERT_FILE, and /nix/store is already present in the sandbox.

Related to https://github.com/NixOS/nixpkgs/issues/409848
2026-08-02 21:48:24 +00:00
ed 5c157be955 show subvolume size in listings;
* descends recursively to all nested volumes
* values are static, update on volume-rescan
* closes #634 #1033 #1390

Co-authored-by: Victor Phillips <victor.m.phillips@protonmail.com>
2026-08-01 22:23:58 +00:00
Wuerfel21 9f5f9d8884
th: .kra (krita) + .ora (openraster) (#1577) 2026-07-30 18:39:31 +00:00
ed 4003a47723 multipart boundary max-len 2026-07-30 18:22:45 +00:00
ed 398fcf1d18 update pkgs to 1.20.19 2026-07-27 22:08:01 +00:00
43 changed files with 196 additions and 42 deletions

1
.gitattributes vendored
View file

@ -4,4 +4,5 @@
*.png binary
*.gif binary
*.ico binary
*.gz binary

View file

@ -137,6 +137,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [client-side](#client-side) - when uploading files
* [security](#security) - there is a [discord server](https://discord.gg/25J8CdTT6G) with announcements
* [gotchas](#gotchas) - behavior that might be unexpected
* [csp nonce](#csp-nonce) - unauthorized javascript is blocked
* [cors](#cors) - cross-site request config
* [filekeys](#filekeys) - prevent filename bruteforcing
* [dirkeys](#dirkeys) - share specific folders in a volume
@ -3107,6 +3108,23 @@ behavior that might be unexpected
* or eliminate the problem entirely by only giving write-access to trustworthy people :^)
## csp nonce
unauthorized javascript is blocked by means of the default [content-security-policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) which enforces "script nonces", to help prevent XSS and similar
usually this Just Works and is correctly configured by default; you can stop reading this section and move on, nothing to see here
but if you are the unlucky 0.1% with a hosting-setup funky enough for this to not work as intended, then you will also be seeing this message: `Looks like javascript is broken; see "csp nonce" in the readme`
there is two possible reasons why this might happen:
1. your browser is simply too old to understand the javascript, which is not likely, because just about every browser is supported, even internet explorer 11
2. something in your hosting-setup is tampering with the responses from copyparty and destroying this security feature; the culprit is probably a misconfigured reverseproxy, a CDN, a WAF, or some other mitm, but a popular offender is **"cloudflare rocket loader"**
you can try to **temporarily** set global-options `--csp-ui no` and `--csp-dl no` and restart copyparty to check whether it is due to reason #2. Remember to refresh the webbrowser with CTRL-SHIFT-R to check properly. If that makes the problem go away, then turn them back off (**never** keep them enabled), restart copyparty, open the `Network` tab in the webbrowser, and refresh the website; you should see the `content-security-policy` header including a nonce, for example `nonce-BxcJbvdu1z3S/ycPBjVWuA==` which should also appear inside the HTML of that response like this: `<script nonce="BxcJbvdu1z3S/ycPBjVWuA==">` -- if it doesn't then that's the problem; something in your hosting stack is dangerously misconfigured
## cors
cross-site request config

View file

@ -17,8 +17,12 @@ defaults
listen foo1
bind *:8081
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-request add-header X-Forwarded-Proto http unless { ssl_fc }
server srv1 127.0.0.1:3923 maxconn 512
listen foo2
bind *:8082
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-request add-header X-Forwarded-Proto http unless { ssl_fc }
server srv1 /dev/shm/party.sock maxconn 512

View file

@ -285,6 +285,7 @@ in
environment = {
PYTHONUNBUFFERED = "true";
NIX_SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
XDG_CONFIG_HOME = externalStateDir;
};

View file

@ -3,7 +3,7 @@
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty
pkgver="1.20.18"
pkgver="1.20.20"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@ -24,7 +24,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
)
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("etc/${pkgname}/copyparty.conf" )
sha256sums=("ea1347a2e30b4261282ac4609c9b844768a925eed717ac8af1ff87a8a326508e")
sha256sums=("a31f9e4a43e56608765e108353140784d50181c37e705e9ce8ed9f3899f57dfa")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -2,7 +2,7 @@
pkgname=copyparty
pkgver=1.20.18
pkgver=1.20.20
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
)
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("/etc/${pkgname}.d/init" )
sha256sums=("ea1347a2e30b4261282ac4609c9b844768a925eed717ac8af1ff87a8a326508e")
sha256sums=("a31f9e4a43e56608765e108353140784d50181c37e705e9ce8ed9f3899f57dfa")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.20.18/copyparty-1.20.18.tar.gz",
"version": "1.20.18",
"hash": "sha256-6hNHouMLQmEoKsRgnJuER2ipJe7XF6yK8f+HqKMmUI4="
"url": "https://github.com/9001/copyparty/releases/download/v1.20.20/copyparty-1.20.20.tar.gz",
"version": "1.20.20",
"hash": "sha256-ox+eSkPlZgh2XhCDUxQHhNUBgcN+cF6c6O2fOJn1ffo="
}

View file

@ -1587,6 +1587,7 @@ def add_wopi(ap):
ap2.add_argument("--wopi", action="store_true", help="enable integration with office suites using WOPI")
ap2.add_argument("--wopi-api", metavar="URL", type=u, default="", help="URL that the WOPI-client should use to communicate with copyparty; default is same as user's webbrowser. Example: [\033[32mhttps://party.example.com/\033[0m]")
ap2.add_argument("--wopi-url", metavar="URL", type=u, default="", help="URL to your WOPI client; the host of e.g. Collabora Online. Example: [\033[32mhttps://code.example.com/\033[0m]")
ap2.add_argument("--wopi-urls", metavar="HOST=URL", type=u, action="append", help="\033[34mREPEATABLE:\033[0m maps http \033[33mHOST\033[0m copyparty being accessed by to specific WOPI client instance \033[33mURL\033[0m; falls back to \033[33m--wopi-url\033[0m; examples: [\033[32mparty.public.com=https://office.public.com/\033[0m], [\033[32mparty.internal.net:8443=https://office.internal.net:8443/\033[0m]")
ap2.add_argument("--wopi-crt", metavar="TXT", type=u, default="", help="if \033[33m--wopi-url\033[0m is selfsigned: path to ca.pem or cert.pem to expect/verify (can be [\033[32mno\033[0m] for full-yolo)")
ap2.add_argument("--wopi-crt-icn", action="store_true", help="if \033[33m--wopi-url\033[0m is selfsigned: ignore the CN (server ip/name) in cert")
ap2.add_argument("--wopi-ttl", metavar="SEC", type=int, default=1800, help="session lifetime; allow editing for this many seconds (default is 30 min)")
@ -1832,14 +1833,14 @@ def add_thumbnail(ap):
# https://github.com/libvips/libvips
# https://stackoverflow.com/a/47612661
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,kra,ora,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="3fr,arw,avif,cr2,cr3,crw,dcr,dng,erf,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,k25,kdc,mdc,mef,mrw,nef,nii,nrw,orf,pfm,pgm,png,ppm,raf,raw,rw2,sr2,srf,srw,svg,tif,tiff,webp,x3f", help="image formats to decode using pyvips")
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,rw2,sr2,srf,srw,x3f", help="image formats to decode using rawpy (if available) or libraw's dcraw_emu")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,kra,ora,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mka,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub, kra=png.kra, ora=png.ora", help="audio/image formats to decompress before passing to ffmpeg")
def add_transcoding(ap):

View file

@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 20, 19)
VERSION = (1, 20, 20)
CODENAME = "sftp is fine too"
BUILD_DT = (2026, 7, 27)
BUILD_DT = (2026, 8, 4)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View file

@ -2660,10 +2660,11 @@ class AuthSrv(object):
if head_s and not head_s.endswith("\n"):
head_s += "\n"
zs2 = "Content-Security-Policy: %s\r\n"
zs = vol.flags.get("csp_ui", "")
csp_ui = "Content-Security-Policy: %s\r\n" % (zs,) if zs else ""
csp_ui = zs2 % (zs,) if zs.lower() not in ("", "no") else ""
zs = vol.flags.get("csp_dl", "")
csp_dl = "Content-Security-Policy: %s\r\n" % (zs,) if zs else ""
csp_dl = zs2 % (zs,) if zs.lower() not in ("", "no") else ""
zs = "X-Content-Type-Options: nosniff\r\n"
if "norobots" in vol.flags:
@ -2761,7 +2762,8 @@ class AuthSrv(object):
for k in drop:
t = 'cannot enable [%s] for volume "/%s" because this requires one of the following: e2d / e2ds / e2dsa (either as volflag or global-option)'
self.log(t % (k, vol.vpath), 1)
if not (enshare and vp.startswith(shrs)):
self.log(t % (k, vol.vpath), 1)
vol.flags.pop(k)
zi = vol.flags.get("lifetime") or 0

View file

@ -1673,14 +1673,18 @@ class HttpCli(object):
uo_kw["context"] = ctx
url = self.args.wopi_url.rstrip("/") + "/hosting/discovery"
wopi_urls = dict(x.lower().split("=", 1) for x in self.args.wopi_urls or [])
url = wopi_urls.get(self.host.lower(), self.args.wopi_url).rstrip("/")
url += "/hosting/discovery"
buf = urlopen(url, **uo_kw).read()
xml = buf.decode("ascii", "replace").lower()
enc = self.get_xml_enc(xml)
xml = buf.decode(enc, "replace")
xroot = parse_xml(xml)
ext = vpath.split(".")[-1]
url = xroot.find(".//action[@ext='%s'][@urlsrc]" % (ext,)).get("urlsrc")
url = xroot.find(
".//action[@ext='%s'][@name='edit'][@urlsrc]" % (ext,)
).get("urlsrc")
if not url.endswith(("?", "&")):
url += "&" if "?" in url else "?"
url += "WOPISrc="
@ -7441,20 +7445,18 @@ class HttpCli(object):
dirs = []
files = []
ptn_hr = RE_HR
use_abs_url = is_opds or (
vpath and not is_ls and not is_js and not self.trailing_slash
)
base = ""
if is_opds or (vpath and not (is_ls or is_js or self.trailing_slash)):
if is_opds:
base = self.args.SRS
if vpath:
base += vpath + "/"
else:
base = "/" + vpath + "/"
for fn in ls_names:
base = ""
href = fn
if use_abs_url:
if is_opds:
base = self.args.SRS
if vpath:
base += vpath + "/"
else:
base = "/" + vpath + "/"
href = base + fn
href = base + fn
if fn in vfs_virt:
fspath = vfs_virt[fn].realpath
@ -7624,6 +7626,26 @@ class HttpCli(object):
(fe["sz"], fe["tags"][".files"]) = hit
except:
pass # 404 or mojibake
if vfs_virt:
q = "select sz, nf from ds where rd='' limit 1"
try:
for fe in [x for x in dirs if x["name"] in vfs_virt]:
if ".files" not in fe["tags"]:
fe["tags"][".files"] = 0
vols = [vn.nodes[fe["name"]]]
while vols:
vn2 = vols.pop()
if self.uname not in vn2.axs.uread:
continue
vols += list(vn2.nodes.values())
if vn2.dbv not in (vn2, None):
continue
hit = idx.get_cur(vn2).execute(q).fetchone()
if hit:
fe["sz"] += hit[0]
fe["tags"][".files"] += hit[1]
except:
pass
taglist = [k for k in lmte if k in tagset]
else:

View file

@ -194,6 +194,12 @@ def au_unpk(
log(t)
fi = zf.open(using)
elif pk == "kra" or pk == "ora":
import zipfile
zf = zipfile.ZipFile(abspath, "r")
fi = zf.open("mergedimage.png")
elif pk == "epub":
fi = get_cover_from_epub(log, abspath)
assert fi # !rm

View file

@ -2150,7 +2150,8 @@ class MultipartParser(object):
boundary = get_boundary(self.headers)
if boundary.startswith('"') and boundary.endswith('"'):
boundary = boundary[1:-1] # dillo uses quotes
self.log("boundary=%r" % (boundary,))
if len(boundary) > 72: # rfc-2046 <=70
raise Pebkac(400, "boundary 2big: %d" % (len(boundary),))
# spec says there might be junk before the first boundary,
# can't have the leading \r\n if that's not the case

View file

@ -121,6 +121,8 @@
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
<p id="js_bork">Looks like javascript is broken; see "csp nonce" section in readme</p>
<a href="#" id="repl">π</a>
</div>

View file

@ -46,6 +46,7 @@ if (1)
["ctrl-V", "paste (move/copy) here"],
["Y", "download selected"],
["F2", "rename selected"],
["F4", "update/refresh"],
"file-list-sel",
["space", "toggle file selection"],
@ -6438,6 +6439,9 @@ var ahotkeys = function (e) {
if (k == 'F2')
return fileman.rename();
if (k == 'F4')
return treectl.goto();
if (!treectl.hidden && (!sh || !thegrid.en)) {
if (kl == 'a')
return QS('#twig').click();
@ -7786,6 +7790,7 @@ var treectl = (function () {
r.hydrate = function () {
qsr('#bbsw');
qsr('#js_bork');
srvinf = ebi('srv_info').innerHTML.slice(6, -7);
if (ls0 === null) {
r.ls_cb = showfile.addlinks;
@ -8872,7 +8877,7 @@ var msel = (function () {
r.evsel = function (e, fun) {
ev(e);
r.so = r.pr = null;
var trs = QSA('#files tbody tr');
var trs = QSA('#files tbody tr' + (ebi('unsearch') ? ':not(.srch_hdr)' : ''));
for (var a = 0, aa = trs.length; a < aa; a++)
clmod(trs[a], 'sel', fun);
r.selui();

View file

@ -39,6 +39,7 @@ Ls.chi = {
["ctrl-V", "粘贴(移动/复制)到此处"],
["Y", "下载选中项"],
["F2", "重命名选中项"],
["F4", "刷新"], //m
"文件列表选择",
["space", "切换文件选择"],

View file

@ -39,6 +39,7 @@ Ls.cze = {
["ctrl-V", "vložit (přesunout/kopírovat) zde"],
["Y", "stáhnout vybrané"],
["F2", "přejmenovat vybrané"],
["F4", "obnovit"], //m
"výběr souborů",
["space", "přepnout výběr souboru"],

View file

@ -39,6 +39,7 @@ Ls.deu = {
["STRG-V", "Zwischenablage hier einfügen"],
["Y", "Auswahl herunterladen"],
["F2", "Auswahl umbenennen"],
["F4", "Aktualisieren"], //m
"file-list-sel",
["LEER", "Dateiauswahl aktivieren"],

View file

@ -39,6 +39,7 @@ Ls.epo = {
["stir-V", "alglui (movi/kopii) ĉi tien"],
["Y", "elŝuti elektitajn"],
["F2", "alinomi elektitajn"],
["F4", "refreŝigi"], //m
"file-list-sel",
["spacoklavo", "baskuli elektadon de dosieroj"],

View file

@ -39,6 +39,7 @@ Ls.fin = {
["ctrl-V", "siirrä tai kopioi tähän"],
["Y", "lataa valitut"],
["F2", "uudelleennimeä valitut"],
["F4", "päivitä"], //m
"file-list-sel",
["space", "vaihda tiedostonvalintatilaan"],

View file

@ -39,6 +39,7 @@ Ls.fra = {
["ctrl-V", "coller (déplacer/copier) ici"],
["Y", "télécharger la sélection"],
["F2", "renomer la sélection"],
["F4", "actualiser"], //m
"file-list-sel",
["Espace", "activer la sélection de fichiers"],

View file

@ -39,6 +39,7 @@ Ls.grc = {
["ctrl-V", "επικόλληση (μετακίνηση/αντιγραφή) εδώ"],
["Y", "λήψη επιλεγμένων"],
["F2", "μετονομασία επιλεγμένων"],
["F4", "ανανέωση"], //m
"λίστα αρχείων",
["space", "εναλλαγή επιλογής αρχείου"],

View file

@ -36,6 +36,7 @@ Ls.hun = {
['ctrl-V', 'beillesztés ide'],
['Y', 'kijelöltek letöltése'],
['F2', 'átnevezés'],
["F4", "frissítés"], //m
'kijelölés',
['space', 'fájl kijelölése'],

View file

@ -39,6 +39,7 @@ Ls.ita = {
["ctrl-V", "incolla (sposta/copia) qui"],
["Y", "scarica selezionati"],
["F2", "rinomina selezionati"],
["F4", "aggiorna"], //m
"file-list-sel",
["spazio", "alterna selezione file"],

View file

@ -39,6 +39,7 @@ Ls.jpn = {
["ctrl-V", "ここに貼り付け(移動/コピー)"],
["Y", "選択した項目をダウンロード"],
["F2", "選択した項目の名前を変更"],
["F4", "更新"], //m
"file-list-sel",
["space", "ファイル選択の切り替え"],

View file

@ -39,6 +39,7 @@ Ls.kor = {
["ctrl-V", "여기에 붙여넣기 (이동/복사)"],
["Y", "선택 항목 다운로드"],
["F2", "선택 항목 이름 바꾸기"],
["F4", "새로 고침"], //m
"파일 목록 선택",
["space", "파일 선택/해제"],

View file

@ -39,6 +39,7 @@ Ls.nld = {
["ctrl-V", "Hier plakken (verplaatsen/kopieëren)"],
["Y", "Download geselecteerde"],
["F2", "Hernoem geselecteerde"],
["F4", "vernieuwen"], //m
"bestand-lijst-selectie",
["space", "wissel bestand selectie"],

View file

@ -36,6 +36,7 @@ Ls.nno = {
["ctrl-V", "lim inn (flytt/kopiér)"],
["Y", "last ned valde"],
["F2", "endre namn på valde"],
["F4", "oppdater"],
"filmarkering",
["space", "markér fil"],

View file

@ -36,6 +36,7 @@ Ls.nor = {
["ctrl-V", "lim inn (flytt/kopiér)"],
["Y", "last ned valgte"],
["F2", "endre navn på valgte"],
["F4", "oppdater"],
"filmarkering",
["space", "marker fil"],

View file

@ -39,6 +39,7 @@ Ls.pol = {
["ctrl-V", "wklej (przenieś/skopiuj) tutaj"],
["Y", "pobierz zaznaczone"],
["F2", "zmień nazwę zaznaczonych"],
["F4", "odśwież"], //m
"file-list-sel",
["spacja", "przełącz zaznaczanie plików"],

View file

@ -39,6 +39,7 @@ Ls.por = {
["ctrl-V", "colar (mover/copiar) aqui"],
["Y", "baixar selecionado"],
["F2", "renomear selecionado"],
["F4", "atualizar"], //m
"seleção de lista de arquivos",
["espaço", "alternar seleção de arquivo"],

View file

@ -39,6 +39,7 @@ Ls.rus = {
["ctrl-V", "вставить (переместить/копировать) сюда"],
["Y", "скачать выделенное"],
["F2", "переименовать выделенное"],
["F4", "обновить"], //m
"выделение файлов",
["пробел", "выделить/снять выделение с текущего файла"],

View file

@ -39,6 +39,7 @@ Ls.spa = {
["ctrl-V", "pegar (mover/copiar) aquí"],
["Y", "descargar seleccionados"],
["F2", "renombrar seleccionados"],
["F4", "actualizar"], //m
"selección en lista de archivos",
["space", "alternar selección de archivo"],

View file

@ -39,6 +39,7 @@ Ls.swe = {
["ctrl-V", "klistra in (kopiera/flytta) hit"],
["Y", "ladda ner urval"],
["F2", "byt namn på urval"],
["F4", "uppdatera"], //m
"välja filer",
["Blanksteg", "växla val av fil"],

View file

@ -39,6 +39,7 @@ Ls.tur = {
["ctrl-V", "buraya yapıştır (taşı/kopyala)"],
["Y", "seçileni indir"],
["F2", "seçileni yeniden adlandır"],
["F4", "yenile"], //m
"dosya yönetimi seçimleri",
["boşluk", "seçimi değiştir"],

View file

@ -39,6 +39,7 @@ Ls.ukr = {
["ctrl-V", "вставити (перемістити/копіювати) сюди"],
["Y", "завантажити вибране"],
["F2", "перейменувати вибране"],
["F4", "оновити"], //m
"вибір файлів у списку",
["space", "перемкнути вибір файлу"],

View file

@ -36,6 +36,7 @@ Ls.vie = {
["ctrl-V", "dán (di chuyển/sao chép) tại đây"],
["Y", "tải xuống mục đã chọn"],
["F2", "đổi tên mục đã chọn"],
["F4", "làm mới"], //m
"file-list-sel",
["space", "chuyển đổi chọn tệp"],

View file

@ -998,6 +998,14 @@ function up2k_init(subtle) {
});
}
function unqueue_up(t) {
t.postlist = [];
var arr = st.todo.upload;
for (var a = arr.length - 1; a >= 0; a--)
if (arr[a].nfile == t.n)
arr.splice(a, 1);
}
var pvis = new U2pvis("bz", '#u2cards', uc, st),
donut = new Donut(uc, st);
@ -2512,6 +2520,7 @@ function up2k_init(subtle) {
keepalive = t.keepalive,
me = Date.now();
while (apop(st.todo.handshake, t));
if (t.done)
return console.log('done; skip hs', t.name, t);
@ -2630,7 +2639,7 @@ function up2k_init(subtle) {
cbd.push(a == cdr_idx ? cdr_sz : chunksize);
}
t.postlist = [];
unqueue_up(t);
t.wark = response.wark;
var missing = response.hash;
for (var a = 0; a < missing.length; a++) {
@ -2919,8 +2928,7 @@ function up2k_init(subtle) {
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
if (txt.indexOf('upload blocked by x') + 1) {
apop(st.busy.upload, upt);
for (var a = pcar; a <= pcdr; a++)
apop(t.postlist, a);
unqueue_up(t);
pvis.seth(t.n, 1, "ERROR");
pvis.seth(t.n, 2, txt.split(/\n/)[0]);
pvis.move(t.n, 'ng');
@ -2943,8 +2951,10 @@ function up2k_init(subtle) {
st.etac.u++;
st.etac.t++;
}
else if (txt.indexOf('already got that') + 1 ||
txt.indexOf('already being written') + 1) {
else if (txt.indexOf('already got that') + 1) {
unqueue_up(t);
}
else if (txt.indexOf('already being written') + 1) {
t.nojoin = t.nojoin || t.postlist.length;
console.log("ignoring dupe-segment with backoff", t.nojoin, t.name, t);
if (!toast.visible && st.todo.upload.length < 4)
@ -2952,6 +2962,10 @@ function up2k_init(subtle) {
}
else {
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), "404, target folder not found (???)", "warn", t);
if (txt.indexOf('unknown wark') == 0) {
t.cooldown = t.coolmul = 0;
unqueue_up(t);
}
chill(t);
}
orz2(xhr);
@ -2962,8 +2976,10 @@ function up2k_init(subtle) {
apop(t.postlist, a);
if (!t.postlist.length) {
t.t_uploaded = Date.now();
pvis.seth(t.n, 1, 'verifying');
st.todo.handshake.unshift(t);
if (!has(st.busy.handshake, t)) {
pvis.seth(t.n, 1, 'verifying');
st.todo.handshake.unshift(t);
}
}
tasker();
}
@ -2998,6 +3014,7 @@ function up2k_init(subtle) {
if (!toast.visible)
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), t);
unqueue_up(t); // maybe unknown wark (ff drops undrained rsp)
t.nojoin = t.nojoin || t.postlist.length; // maybe rproxy postsize limit
console.log('chunkpit onerror,', t.name, t);
orz2(xhr);

View file

@ -1199,8 +1199,10 @@ function has(haystack, needle) {
function apop(arr, v) {
var ofs = arr.indexOf(v);
if (ofs !== -1)
if (ofs !== -1) {
arr.splice(ofs, 1);
return true;
}
}
@ -1466,7 +1468,7 @@ function Debounce(delay) {
};
r.rm = function (fun) {
apop(r.q, fun);
while (apop(r.q, fun));
};
r.run = function () {
@ -1526,7 +1528,7 @@ var timer = (function () {
};
r.rm = function (fun) {
apop(r.q, fun);
while (apop(r.q, fun));
};
var doevents = function () {

View file

@ -1,3 +1,52 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2026-0727-2202 `v1.20.19` SECURITY: fix FTP upload outside volume
## ⚠️ ATTN: this release fixes a vulnerability in FTP and FTPS (not SFTP)
the FTP-server (default-disabled) would allow uploading to any folder that the copyparty process had permission to write to, but with certain limitations; see [GHSA-phv8-wgjp-g4p9](https://github.com/9001/copyparty/security/advisories/GHSA-phv8-wgjp-g4p9)
## recent important news
* [v1.20.19 (2026-07-27)](https://github.com/9001/copyparty/releases/tag/v1.20.19) fixed an FTP-server vuln (upload outside defined volumes)
* [v1.20.17 (2026-07-06)](https://github.com/9001/copyparty/releases/tag/v1.20.17) fixed a vuln when a volume has both filekeys and dirkeys enabled
* [v1.20.17 (2026-07-06)](https://github.com/9001/copyparty/releases/tag/v1.20.17) introduced csp nonces, possibly breaking some javascript-based plugins
## 🧪 new features
* #1495 wopi integration -- edit office documents in the web-ui (thx @brandon-doornbos!) d57bb0c7 10db4236
* currently only works with collabora online as wopi client #1574 and there's no docs/examples #1575
* thumbnails: use embedded cover-image in videos when available 59524018
* thumbnails: folder-thumbs can be disabled with `th-covers: no` (volflag or global) 856fada3
* #1555 macos: add [--srch-nfkc](https://copyparty.eu/cli/#g-srch-nfkc) to fix searching for filenames / paths in CJK languages a7c99094
* reduces search performance to around 30% when enabled
* hooks: `xbr` / `xar` did not include old/new abspath as parameters; now they do c122e103
## 🩹 bugfixes
* ftp: fix [GHSA-phv8-wgjp-g4p9](https://github.com/9001/copyparty/security/advisories/GHSA-phv8-wgjp-g4p9) b331bb1c
* #1563 moving files between volumes could fail depending on OS and underlying filesystem c70dc7ac
* fix drag-drop uploading in certain glitchy KDE environments (thx @tilse!) daf144af
* hooks: `xiu` crashed if the fork-flag was set (thx @stackxp!) aa862353
* hooks: `xau` without json-flag would be given the wark (file hash) instead of the abspath bae77b90
* shares: fix markdown-viewer (`?v`) inside shares 6a9437b7
* ftp: fix logging from `xbu` hooks 9912a951
* fix slow boot if a volume had lots of files in its toplevel folder cdb474c8
* python2.7: fix multithreaded file-hashing 0f2040c3
## 🔧 other changes
* #1556 the libvips thumbnailer was demoted to last-fallback due to frequently using excessive amounts of ram ed0be42a
* if [-lo](https://copyparty.eu/cli/#g-lo) points to an existing file, it will now be appended to instead of overwritten depending on [--rlo](https://copyparty.eu/cli/#g-help-rlo) b6abc33f
* #1530 nixos: the nix package now uses `ffmpeg-headless` instead of `ffmpeg-full` (thx @nyakase!) fface524
* slightly longer session cookie (was 20, now 24 chars) 537a99df
* Windows-specific:
* add a warning regarding the risks of DLL-hijacking when relevant bb40804f
* fix some trivial PATH-related footguns cea97ac6
* faster creation of sparse files bc45299b
* fix detection of filesystem characteristics 6226858b
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2026-0709-2254 `v1.20.18` bwrap off

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -71,6 +71,7 @@ Ls.hmn = {
["ctrl-V", "paste (move/copy) here"],
["Y", "download selected"],
["F2", "rename selected"],
["F4", "update/refresh"],
"file-list-sel",
["space", "toggle file selection"],