Compare commits

...

7 Commits

Author SHA1 Message Date
ed
6aaafeee6d v1.16.18 2025-03-23 22:16:40 +00:00
ed
99f63adf58 google isn't taking the hint
specifically google, but also some others, have started ignoring
rel="nofollow" while also understanding just enough javascript to
try viewing binary files as text
2025-03-23 21:21:41 +00:00
ed
de2c978842 docker: suggest mimalloc 2025-03-23 20:45:03 +00:00
ed
3c90cec0cd forgot these
pyinstaller/build.sh: fix jinja2 after upgrade

up2k.py: fix double-hs after dupe finalize
2025-03-23 20:19:18 +00:00
ed
57a56073d8 use zlib-ng when available
download-as-tar-gz becomes 2.4x faster in docker

segfaults on windows, so don't use it there

does not affect fedora or gentoo,
since zlib-ng is already system-default on those

also adds a global-option to write list of successful
binds to a textfile, for automation / smoketest purposes
2025-03-23 20:15:21 +00:00
ed
2525d594c5 19a5985f removed the restriction on uploading logues, as it was
too restrictive, blocking editing through webdav and ftp

but since logues and readmes can be used as helptext for users
with write-only access, it makes sense to block logue/readme
uploads from write-only users

users with write-only access can still upload any file as before,
but the filename prefix `_wo_` is added onto files named either
README.md | PREADME.md | .prologue.html | .epilogue.html

the new option `--wo-up-readme` restores previous behavior, and
will not add the filename-prefix for readmes/logues
2025-03-22 14:21:35 +00:00
ed
a0ecc4d88e update pkgs to 1.16.17 2025-03-16 21:13:23 +00:00
29 changed files with 256 additions and 44 deletions

View File

@@ -100,7 +100,7 @@ turn almost any device into a file server with resumable uploads/downloads using
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
* [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
* [feature beefybits](#feature-beefybits) - force-enable incompatible features
* [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
* [packages](#packages) - the party might be closer than you think
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
* [fedora package](#fedora-package) - does not exist yet

View File

@@ -2,11 +2,15 @@
import sys
import json
import zlib
import struct
import base64
import hashlib
try:
from zlib_ng import zlib_ng as zlib
except:
import zlib
try:
from copyparty.util import fsenc
except:

View File

@@ -1,6 +1,6 @@
# Maintainer: icxes <dev.null@need.moe>
pkgname=copyparty
pkgver="1.16.16"
pkgver="1.16.17"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@@ -22,7 +22,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=("5ad7a70c4369633a297fb6904710a1e429d2d17ee46e8e8e5e40c3edeee229d7")
sha256sums=("6dba0df650bfa6c47ebffcd0c9ef450b49dd998b87265778470799f7cdcd6b00")
build() {
cd "${srcdir}/${pkgname}-${pkgver}"

View File

@@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.16.16/copyparty-sfx.py",
"version": "1.16.16",
"hash": "sha256-Fyz2QEM6xEMXfFAODgg71XfvpZ0b6cY4JidfsnKHIEg="
"url": "https://github.com/9001/copyparty/releases/download/v1.16.17/copyparty-sfx.py",
"version": "1.16.17",
"hash": "sha256-D3hz4tr0/Qb8ySZvhI/eKTUvONbmb8RbwzTEHMWpA6o="
}

View File

@@ -40,6 +40,7 @@ from .cfg import flagcats, onedash
from .svchub import SvcHub
from .util import (
APPLESAN_TXT,
BAD_BOTS,
DEF_EXP,
DEF_MTE,
DEF_MTH,
@@ -1029,6 +1030,8 @@ def add_network(ap):
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
else:
ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
ap2.add_argument("--wr-h-eps", metavar="PATH", type=u, default="", help="write list of listening-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
ap2.add_argument("--wr-h-aon", metavar="PATH", type=u, default="", help="write list of accessible-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=128.0, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
@@ -1222,6 +1225,7 @@ def add_yolo(ap):
ap2 = ap.add_argument_group('yolo options')
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
def add_optouts(ap):
@@ -1241,6 +1245,7 @@ def add_optouts(ap):
ap2.add_argument("--zipmaxt", metavar="TXT", type=u, default="", help="custom errormessage when download size exceeds max (volflag=zipmaxt)")
ap2.add_argument("--zipmaxu", action="store_true", help="authenticated users bypass the zip size limit (volflag=zipmaxu)")
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")
ap2.add_argument("--ua-nozip", metavar="PTN", type=u, default=BAD_BOTS, help="regex of user-agents to reject from download-as-zip/tar; disable with [\033[32mno\033[0m] or blank")
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar; same as \033[33m--zip-who=0\033[0m")
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
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")
@@ -1431,6 +1436,7 @@ def add_txt(ap):
ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)")
ap2.add_argument("--exp-md", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md)")
ap2.add_argument("--exp-lg", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg)")
ap2.add_argument("--ua-nodoc", metavar="PTN", type=u, default=BAD_BOTS, help="regex of user-agents to reject from viewing documents through ?doc=[...]; disable with [\033[32mno\033[0m] or blank")
def add_og(ap):

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 16, 17)
VERSION = (1, 16, 18)
CODENAME = "COPYparty"
BUILD_DT = (2025, 3, 16)
BUILD_DT = (2025, 3, 23)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -52,6 +52,7 @@ def vf_bmap() -> dict[str, str]:
"og_s_title",
"rand",
"rss",
"wo_up_readme",
"xdev",
"xlink",
"xvol",
@@ -173,6 +174,7 @@ flagcats = {
"vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
"vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
"medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
"wo_up_readme": "write-only users can upload logues without getting renamed",
"rand": "force randomized filenames, 9 chars long by default",
"nrand=N": "randomized filenames are N chars long",
"u2ow=N": "overwrite existing files? 0=no 1=if-older 2=always",

View File

@@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .util import (
FN_EMB,
VF_CAREFUL,
Daemon,
ODict,
@@ -170,6 +171,16 @@ class FtpFs(AbstractedFS):
fn = sanitize_fn(fn or "", "")
vpath = vjoin(rd, fn)
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
if (
w
and fn.lower() in FN_EMB
and self.h.uname not in vfs.axs.uread
and "wo_up_readme" not in vfs.flags
):
fn = "_wo_" + fn
vpath = vjoin(rd, fn)
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
if not vfs.realpath:
t = "No filesystem mounted at [{}]"
raise FSE(t.format(vpath))

View File

@@ -4,7 +4,6 @@ from __future__ import print_function, unicode_literals
import argparse # typechk
import copy
import errno
import gzip
import hashlib
import itertools
import json
@@ -46,6 +45,7 @@ from .util import (
APPLESAN_RE,
BITNESS,
DAV_ALLPROPS,
FN_EMB,
HAVE_SQLITE3,
HTTPCODE,
META_NOBOTS,
@@ -69,6 +69,7 @@ from .util import (
get_df,
get_spd,
guess_mime,
gzip,
gzip_file_orig_sz,
gzip_orig_sz,
has_resource,
@@ -2550,6 +2551,16 @@ class HttpCli(object):
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
dbv, vrem = vfs.get_dbv(rem)
name = sanitize_fn(name, "")
if (
not self.can_read
and self.can_write
and name.lower() in FN_EMB
and "wo_up_readme" not in dbv.flags
):
name = "_wo_" + name
body["name"] = name
body["vtop"] = dbv.vpath
body["ptop"] = dbv.realpath
body["prel"] = vrem
@@ -3796,6 +3807,9 @@ class HttpCli(object):
return "download-as-zip/tar is admin-only on this server"
elif lvl <= 2 and self.uname in ("", "*"):
return "you must be authenticated to download-as-zip/tar on this server"
elif self.args.ua_nozip and self.args.ua_nozip.search(self.ua):
t = "this URL contains no valuable information for bots/crawlers"
raise Pebkac(403, t)
return ""
def tx_res(self, req_path: str) -> bool:
@@ -6280,6 +6294,10 @@ class HttpCli(object):
doc = self.uparam.get("doc") if self.can_read else None
if doc:
zp = self.args.ua_nodoc
if zp and zp.search(self.ua):
t = "this URL contains no valuable information for bots/crawlers"
raise Pebkac(403, t)
j2a["docname"] = doc
doctxt = None
dfn = lnames.get(doc.lower())

View File

@@ -18,6 +18,7 @@ from .util import (
REKOBO_LKEY,
VF_CAREFUL,
fsenc,
gzip,
min_ex,
pybin,
retchk,
@@ -138,8 +139,6 @@ def au_unpk(
fd, ret = tempfile.mkstemp("." + au)
if pk == "gz":
import gzip
fi = gzip.GzipFile(abspath, mode="rb")
elif pk == "xz":

View File

@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
import argparse
import errno
import gzip
import logging
import os
import re
@@ -63,6 +62,7 @@ from .util import (
ansi_re,
build_netmap,
expat_ver,
gzip,
load_ipu,
min_ex,
mp,
@@ -769,7 +769,8 @@ class SvcHub(object):
vs = os.path.expandvars(os.path.expanduser(vs))
setattr(al, k, vs)
for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
for k in zs.split(" "):
vs = getattr(al, k)
if not vs or vs == "no":
setattr(al, k, None)

View File

@@ -4,12 +4,11 @@ from __future__ import print_function, unicode_literals
import calendar
import stat
import time
import zlib
from .authsrv import AuthSrv
from .bos import bos
from .sutil import StreamArc, errdesc
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile, zlib
if True: # pylint: disable=using-constant-test
from typing import Any, Generator, Optional

View File

@@ -151,9 +151,15 @@ class TcpSrv(object):
if just_ll or self.args.ll:
ll_ok.add(ip.split("/")[0])
listening_on = []
for ip, ports in sorted(ok.items()):
for port in sorted(ports):
listening_on.append("%s %s" % (ip, port))
qr1: dict[str, list[int]] = {}
qr2: dict[str, list[int]] = {}
msgs = []
accessible_on = []
title_tab: dict[str, dict[str, int]] = {}
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
@@ -169,6 +175,10 @@ class TcpSrv(object):
):
continue
zs = "%s %s" % (ip, port)
if zs not in accessible_on:
accessible_on.append(zs)
proto = " http"
if self.args.http_only:
pass
@@ -219,6 +229,14 @@ class TcpSrv(object):
else:
print("\n", end="")
for fn, ls in (
(self.args.wr_h_eps, listening_on),
(self.args.wr_h_aon, accessible_on),
):
if fn:
with open(fn, "wb") as f:
f.write(("\n".join(ls)).encode("utf-8"))
if self.args.qr or self.args.qrs:
self.qr = self._qr(qr1, qr2)

View File

@@ -36,7 +36,19 @@ from partftpy.TftpShared import TftpException
from .__init__ import EXE, PY2, TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .util import UTC, BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
from .util import (
FN_EMB,
UTC,
BytesIO,
Daemon,
ODict,
exclude_dotfiles,
min_ex,
runhook,
undot,
vjoin,
vsplit,
)
if True: # pylint: disable=using-constant-test
from typing import Any, Union
@@ -244,16 +256,25 @@ class Tftpd(object):
for srv in srvs:
srv.stop()
def _v2a(self, caller: str, vpath: str, perms: list, *a: Any) -> tuple[VFS, str]:
def _v2a(
self, caller: str, vpath: str, perms: list, *a: Any
) -> tuple[VFS, str, str]:
vpath = vpath.replace("\\", "/").lstrip("/")
if not perms:
perms = [True, True]
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
if perms[1] and "*" not in vfs.axs.uread and "wo_up_readme" not in vfs.flags:
zs, fn = vsplit(vpath)
if fn.lower() in FN_EMB:
vpath = vjoin(zs, "_wo_" + fn)
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
if not vfs.realpath:
raise Exception("unmapped vfs")
return vfs, vfs.canonical(rem)
return vfs, vpath, vfs.canonical(rem)
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
# generate file listing if vpath is dir.txt and return as file object
@@ -331,7 +352,7 @@ class Tftpd(object):
else:
raise Exception("bad mode %s" % (mode,))
vfs, ap = self._v2a("open", vpath, [rd, wr])
vfs, vpath, ap = self._v2a("open", vpath, [rd, wr])
if wr:
if "*" not in vfs.axs.uwrite:
yeet("blocked write; folder not world-writable: /%s" % (vpath,))
@@ -368,7 +389,7 @@ class Tftpd(object):
return open(ap, mode, *a, **ka)
def _mkdir(self, vpath: str, *a) -> None:
vfs, ap = self._v2a("mkdir", vpath, [])
vfs, _, ap = self._v2a("mkdir", vpath, [False, True])
if "*" not in vfs.axs.uwrite:
yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
@@ -376,7 +397,7 @@ class Tftpd(object):
def _unlink(self, vpath: str) -> None:
# return bos.unlink(self._v2a("stat", vpath, *a)[1])
vfs, ap = self._v2a("delete", vpath, [True, False, False, True])
vfs, _, ap = self._v2a("delete", vpath, [True, False, False, True])
try:
inf = bos.stat(ap)
@@ -400,7 +421,7 @@ class Tftpd(object):
def _p_exists(self, vpath: str) -> bool:
try:
ap = self._v2a("p.exists", vpath, [False, False])[1]
ap = self._v2a("p.exists", vpath, [False, False])[2]
bos.stat(ap)
return True
except:
@@ -408,7 +429,7 @@ class Tftpd(object):
def _p_isdir(self, vpath: str) -> bool:
try:
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[1])
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[2])
ret = stat.S_ISDIR(st.st_mode)
return ret
except:

View File

@@ -2,7 +2,6 @@
from __future__ import print_function, unicode_literals
import errno
import gzip
import hashlib
import json
import math
@@ -42,6 +41,7 @@ from .util import (
fsenc,
gen_filekey,
gen_filekey_dbg,
gzip,
hidedir,
humansize,
min_ex,
@@ -2918,7 +2918,6 @@ class Up2k(object):
if ptop not in self.registry:
raise Pebkac(410, "location unavailable")
cj["name"] = sanitize_fn(cj["name"], "")
cj["poke"] = now = self.db_act = self.vol_act[ptop] = time.time()
wark = dwark = self._get_wark(cj)
job = None
@@ -3236,6 +3235,7 @@ class Up2k(object):
job["ptop"] = vfs.realpath
job["vtop"] = vfs.vpath
job["prel"] = rem
job["name"] = sanitize_fn(job["name"], "")
if zvfs.vpath != vfs.vpath:
# print(json.dumps(job, sort_keys=True, indent=4))
job["hash"] = cj["hash"]
@@ -3709,8 +3709,9 @@ class Up2k(object):
if self.idx_wark(vflags, *z2):
del self.registry[ptop][wark]
else:
for k in "host tnam busy sprs poke t0c".split():
for k in "host tnam busy sprs poke".split():
del job[k]
job.pop("t0c", None)
job["t0"] = int(job["t0"])
job["hash"] = []
job["done"] = 1
@@ -4996,6 +4997,7 @@ class Up2k(object):
job["ptop"] = vfs.realpath
job["vtop"] = vfs.vpath
job["prel"] = rem
job["name"] = sanitize_fn(job["name"], "")
if zvfs.vpath != vfs.vpath:
self.log("xbu reloc2:%d..." % (depth,), 6)
return self._handle_json(job, depth + 1)

View File

@@ -31,6 +31,17 @@ from collections import Counter
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
from queue import Queue
try:
from zlib_ng import gzip_ng as gzip
from zlib_ng import zlib_ng as zlib
sys.modules["gzip"] = gzip
# sys.modules["zlib"] = zlib
# `- somehow makes tarfile 3% slower with default malloc, and barely faster with mimalloc
except:
import gzip
import zlib
from .__init__ import (
ANYWIN,
EXE,
@@ -234,6 +245,9 @@ SYMTIME = PY36 and os.utime in os.supports_follow_symlinks
META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
# smart enough to understand javascript while also ignoring rel="nofollow"
BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
URL_PRJ = "https://github.com/9001/copyparty"
@@ -448,6 +462,8 @@ UNHUMANIZE_UNITS = {
VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
FN_EMB = set([".prologue.html", ".epilogue.html", "readme.md", "preadme.md"])
def read_ram() -> tuple[float, float]:
a = b = 0
@@ -1451,8 +1467,6 @@ def stackmon(fp: str, ival: float, suffix: str) -> None:
buf = st.encode("utf-8", "replace")
if fp.endswith(".gz"):
import gzip
# 2459b 2304b 2241b 2202b 2194b 2191b lv3..8
# 0.06s 0.08s 0.11s 0.13s 0.16s 0.19s
buf = gzip.compress(buf, compresslevel=6)
@@ -4053,9 +4067,22 @@ class WrongPostKey(Pebkac):
self.datagen = datagen
_: Any = (mp, BytesIO, quote, unquote, SQLITE_VER, JINJA_VER, PYFTPD_VER, PARTFTPY_VER)
_: Any = (
gzip,
mp,
zlib,
BytesIO,
quote,
unquote,
SQLITE_VER,
JINJA_VER,
PYFTPD_VER,
PARTFTPY_VER,
)
__all__ = [
"gzip",
"mp",
"zlib",
"BytesIO",
"quote",
"unquote",

View File

@@ -1,3 +1,43 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0316-2002 `v1.16.17` boot2party
## NEW: make it a bootable usb flashdrive
get the party going anywhere, anytime, no OS required! [download flashdrive image](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) or watch the [low-effort demo video](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/hub-demo-hq.webm) which eventually gets to the copyparty part after showing off a bunch of other stuff on there
* there is [source code](https://github.com/9001/asm/tree/hovudstraum/p/hub) and [build instructions](https://github.com/9001/asm/tree/hovudstraum/p/hub/sm/how2build) too
* please don't take this too seriously
## 🧪 new features
* option to specify max-size for download-as-zip/tar 494179bd 0a33336d
* either the total download size (`--zipmaxs 500M`), and/or max number of files (`--zipmaxn 9k`)
* applies to all uesrs by default; can also ignore limits for authorized users (`--zipmaxu`)
* errormessage can be customized with `--zipmaxt "winter is coming... but this download isn't"`
* [appledoubles](https://a.ocv.me/pub/stuff/?doc=appledoubles-and-friends.txt) are detected and skipped when uploading with the browser-UI 78208405
* IdP-volumes can be filtered by group 9c2c4237
* `[/users/${u}]` in a config-file creates the volume for all users like before
* `[/users/${u%+canwrite}]` only if the user is in the `canwrite` group
* `[/users/${u%-admins}]` only if the user is NOT in the `admins` group
## 🩹 bugfixes
* when moving a folder with symlinks, don't expand them into full files 5ab09769
* absolute symlinks are moved as-is; relative symlinks are rewritten so they still point to the same file when possible (if both source and destination are indexed in the db)
* the previous behavior was good for un-deduplicating files after changing the server-settings, but was too inconvenient for all other usecases
* #146 fix downloading from shares when `-j0` enabled 8417098c
* only show the download-as-zip link when the user is actually allowed to 14bb2999
* the suggestions in the serverlog regarding how to fix incorrect X-Forwarded-For settings would be incorrect if the reverse-proxy used IPv6 to communicate with copyparty 16462ee5
* set nofollow on `?doc` links so crawlers don't download binary files as text 6a2644fe
## 🔧 other changes
* #147 IdP: fix the warning about dangerous misconfigurations to be more accurate 29a17ae2
* #143 print a warning on incorrect character-encoding in textfiles (config-files, logues, readmes etc.) 25974d66
* copyparty.exe: update to jinja 3.1.6 (copyparty was *not affected* by the jinja-3.1.5 vuln)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0228-1846 `v1.16.16` lemon melon cookie

View File

@@ -12,7 +12,7 @@ ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
# versioncheck:
# https://github.com/markedjs/marked/releases
# https://github.com/Ionaru/easy-markdown-editor/tags
# https://github.com/Ionaru/easy-markdown-editor/tags # ignore 2.20.0
# https://github.com/codemirror/codemirror5/releases
# https://github.com/cure53/DOMPurify/releases
# https://github.com/Daninet/hash-wasm/releases

View File

@@ -8,12 +8,13 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
ENV XDG_CONFIG_HOME=/cfg
RUN apk --no-cache add !pyc \
tzdata wget \
tzdata wget mimalloc2 mimalloc2-insecure \
py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \
ffmpeg
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh && rm innvikler.sh
ADD base ./base
RUN ash innvikler.sh ac
WORKDIR /w
EXPOSE 3923

View File

@@ -11,7 +11,7 @@ COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/
RUN apk add -U !pyc \
tzdata wget \
tzdata wget mimalloc2 mimalloc2-insecure \
py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \
py3-pip py3-cffi \
ffmpeg \
@@ -31,7 +31,8 @@ RUN apk add -U !pyc \
&& ln -s /root/vamp /root/.local /
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh && rm innvikler.sh
ADD base ./base
RUN ash innvikler.sh dj
WORKDIR /w
EXPOSE 3923

View File

@@ -8,11 +8,12 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
ENV XDG_CONFIG_HOME=/cfg
RUN apk --no-cache add !pyc \
tzdata wget \
tzdata wget mimalloc2 mimalloc2-insecure \
py3-jinja2 py3-argon2-cffi py3-pillow py3-mutagen
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh && rm innvikler.sh
ADD base ./base
RUN ash innvikler.sh im
WORKDIR /w
EXPOSE 3923

View File

@@ -8,7 +8,7 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
ENV XDG_CONFIG_HOME=/cfg
RUN apk add -U !pyc \
tzdata wget \
tzdata wget mimalloc2 mimalloc2-insecure \
py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \
py3-pip py3-cffi \
ffmpeg \
@@ -21,7 +21,8 @@ RUN apk add -U !pyc \
&& apk del py3-pip .bd
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh && rm innvikler.sh
ADD base ./base
RUN ash innvikler.sh iv
WORKDIR /w
EXPOSE 3923

View File

@@ -11,7 +11,7 @@ RUN apk --no-cache add !pyc \
py3-jinja2
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh && rm innvikler.sh
RUN ash innvikler.sh min
WORKDIR /w
EXPOSE 3923

View File

@@ -101,6 +101,14 @@ the following advice is best-effort and not guaranteed to be entirely correct
* copyparty will generally create a `.hist` folder at the top of each volume, which contains the filesystem index, thumbnails and such. For performance reasons, but also just to keep things tidy, it might be convenient to store these inside the config folder instead. Add the line `hist: /cfg/hists/` inside the `[global]` section of your `copyparty.conf` to do this
* if you want more performance, and you're OK with doubling the RAM usage, then consider enabling mimalloc **(maybe buggy)** with one of these:
* `-e LD_PRELOAD=/usr/lib/libmimalloc-secure.so.2` makes download-as-zip **3x** as fast, filesystem-indexing **1.5x** as fast, etc.
* `-e LD_PRELOAD=/usr/lib/libmimalloc-insecure.so.2` adds another 10% speed but makes it easier to exploit future vulnerabilities
* complete example: `podman run --rm -it -p 3923:3923 -v "$PWD:/w:z" -e LD_PRELOAD=/usr/lib/libmimalloc-secure.so.2 copyparty/ac -v /w::r`
## enabling the ftp server

View File

@@ -0,0 +1,5 @@
FROM alpine:latest
WORKDIR /z
RUN apk add py3-pip make gcc musl-dev python3-dev
RUN pip wheel https://files.pythonhosted.org/packages/c4/a7/0b7673be5945071e99364a3ac1987b02fc1d416617e97f3e8816d275174e/zlib_ng-0.5.1.tar.gz

View File

@@ -0,0 +1,15 @@
self := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
all:
# build zlib-ng from source so we know how the sausage was made
# (still only doing the archs which are officially supported/tested)
podman build --arch amd64 -t localhost/cpp-zlibng-amd64:latest -f Dockerfile.zlibng .
podman run --arch amd64 --rm --log-driver=none -i localhost/cpp-zlibng-amd64:latest tar -cC/z . | tar -xv
podman build --arch arm64 -t localhost/cpp-zlibng-amd64:latest -f Dockerfile.zlibng .
podman run --arch arm64 --rm --log-driver=none -i localhost/cpp-zlibng-amd64:latest tar -cC/z . | tar -xv
sh:
@printf "\n\033[1;31mopening a shell in the most recently created docker image\033[0m\n"
docker run --rm -it --entrypoint /bin/ash `docker images -aq | head -n 1`

View File

@@ -1,6 +1,16 @@
#!/bin/ash
set -ex
# use zlib-ng if available
f=/z/base/zlib_ng-0.5.1-cp312-cp312-linux_$(uname -m).whl
[ "$1" != min ] && [ -e $f ] && {
apk add -t .bd !pyc py3-pip
rm -f /usr/lib/python3*/EXTERNALLY-MANAGED
pip install $f
apk del .bd
}
rm -rf /z/base
# cleanup for flavors with python build steps (dj/iv)
rm -rf /var/cache/apk/* /root/.cache
@@ -40,7 +50,29 @@ find -name __pycache__ |
cd /z
python3 -m copyparty \
--ign-ebind -p$((1024+RANDOM)),$((1024+RANDOM)),$((1024+RANDOM)) \
--no-crt -qi127.1 --exit=idx -e2dsa -e2ts
-v .::r --no-crt -qi127.1 --exit=idx -e2dsa -e2ts
########################################################################
# test download-as-tar.gz
t=$(mktemp)
python3 -m copyparty \
--ign-ebind -p$((1024+RANDOM)),$((1024+RANDOM)),$((1024+RANDOM)) \
-v .::r --no-crt -qi127.1 --wr-h-eps $t & pid=$!
for n in $(seq 1 200); do sleep 0.2
v=$(awk '/^127/{print;n=1;exit}END{exit n-1}' $t) && break
done
[ -z "$v" ] && echo SNAAAAAKE && exit 1
wget -O- http://${v/ /:}/?tar=gz:1 | tar -xzO top/innvikler.sh | cmp innvikler.sh
kill $pid; wait $pid
########################################################################
# output from -e2d
rm -rf .hist
# goodbye
exec rm innvikler.sh

View File

@@ -79,7 +79,6 @@ excl=(
email.parser
importlib.resources
importlib_resources
inspect
multiprocessing
packaging
pdb
@@ -99,6 +98,7 @@ excl=(
PIL.ImageWin
PIL.PdfParser
) || excl+=(
inspect
PIL
PIL.ExifTags
PIL.Image

View File

@@ -129,13 +129,13 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0):
ka = {}
ex = "chpw daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg srch_excl stats uqe vague_403 vc ver write_uplog xdev xlink xvol zipmaxu zs"
ex = "chpw daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
ka.update(**{k: False for k in ex.split()})
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
ka.update(**{k: True for k in ex.split()})
ex = "ah_cli ah_gen css_browser hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
ex = "ah_cli ah_gen css_browser hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip"
ka.update(**{k: None for k in ex.split()})
ex = "hash_mt hsortn safe_dedup srch_time u2abort u2j u2sz"