Compare commits

...

9 Commits

Author SHA1 Message Date
ed
1441ccee4f v1.8.4 2023-07-18 07:46:22 +00:00
ed
491803d8b7 update pkgs to 1.8.3 2023-07-16 23:03:30 +00:00
ed
3dcc386b6f v1.8.3 2023-07-16 22:00:04 +00:00
ed
5aa54d1217 shift/ctrl-click improvements:
* always enable shift-click selection in list-view
* shift-clicking thumbnails opens in new window by default as expected
* enable shift-select in grid-view when multiselect is on
* invert select when the same shift-select is made repeatedly
2023-07-16 18:15:56 +00:00
ed
88b876027c option to range-select files with shift-click; closes #47
also restores the browser-default behavior of
opening links in a new tab with CTRL / new window with SHIFT
2023-07-16 14:05:09 +00:00
ed
fcc3aa98fd add path-traversal scanners 2023-07-16 13:09:31 +00:00
ed
f2f5e266b4 support listing uploader IPs in d2t volumes 2023-07-15 18:50:35 +00:00
ed
e17bf8f325 require the new admin permission for the admin-panel 2023-07-15 18:39:41 +00:00
ed
d19cb32bf3 update pkgs to 1.8.2 2023-07-14 16:05:57 +00:00
19 changed files with 397 additions and 60 deletions

View File

@@ -327,7 +327,7 @@ upgrade notes
# accounts and volumes
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if logged in as admin)
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if the user has `a`/admin in any volume)
* changes to the `[global]` config section requires a restart to take effect
a quick summary can be seen using `--help-accounts`
@@ -346,7 +346,7 @@ permissions:
* `d` (delete): delete files/folders
* `g` (get): only download files, cannot see folder contents or zip/tar
* `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below)
* `a` (admin): can see uploader IPs
* `a` (admin): can see uploader IPs, config-reload
examples:
* add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
@@ -491,6 +491,9 @@ images with the following names (see `--th-covers`) become the thumbnail of the
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
* indicated by the audio files having the ▶ icon instead of 💾
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
* `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
## zip downloads
@@ -613,6 +616,7 @@ file selection: click somewhere on the line (not the link itsef), then:
* `up/down` to move
* `shift-up/down` to move-and-select
* `ctrl-shift-up/down` to also scroll
* shift-click another line for range-select
* cut: select some files and `ctrl-x`
* paste: `ctrl-v` in another folder
@@ -774,7 +778,7 @@ for the above example to work, add the commandline argument `-e2ts` to also scan
using arguments or config files, or a mix of both:
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf)
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
* or click the `[reload cfg]` button in the control-panel when logged in as admin
* or click the `[reload cfg]` button in the control-panel if the user has `a`/admin in any volume
* changes to the `[global]` config section requires a restart to take effect

View File

@@ -138,7 +138,7 @@ in {
"d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads
"a" (upget): can see uploader IPs
"a" (upget): can see uploader IPs, config-reload
For example: "rwmd"

View File

@@ -1,6 +1,6 @@
# Maintainer: icxes <dev.null@need.moe>
pkgname=copyparty
pkgver="1.8.1"
pkgver="1.8.3"
pkgrel=1
pkgdesc="Portable file sharing hub"
arch=("any")
@@ -20,7 +20,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=("f43da11ba5d1d5adf99ad642bf068042c46c23d408e7ed17b025065121abab94")
sha256sums=("6903106cab52536e5273f385813884b9c6dc734ee971ddddacfef8af6b7fec9b")
build() {
cd "${srcdir}/${pkgname}-${pkgver}"

View File

@@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.8.1/copyparty-sfx.py",
"version": "1.8.1",
"hash": "sha256-0Lf5djrgGAM+wwZP66GtSXkmRnIp3tij8j7cANeoE7o="
"url": "https://github.com/9001/copyparty/releases/download/v1.8.3/copyparty-sfx.py",
"version": "1.8.3",
"hash": "sha256-jV9DUp2+lxhLP4QlIYtMoE0Woum9W4i6U/oLDyYyoRE="
}

View File

@@ -492,7 +492,7 @@ def get_sects():
"d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads
"a" (admin): can see uploader IPs
"a" (admin): can see uploader IPs, config-reload
too many volflags to list here, see --help-flags

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 8, 2)
VERSION = (1, 8, 4)
CODENAME = "argon"
BUILD_DT = (2023, 7, 14)
BUILD_DT = (2023, 7, 18)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -324,6 +324,7 @@ class VFS(object):
self.adel: dict[str, list[str]] = {}
self.aget: dict[str, list[str]] = {}
self.apget: dict[str, list[str]] = {}
self.aadmin: dict[str, list[str]] = {}
if realpath:
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
@@ -1182,7 +1183,7 @@ class AuthSrv(object):
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
vol.root = vfs
for perm in "read write move del get pget".split():
for perm in "read write move del get pget admin".split():
axs_key = "u" + perm
unames = ["*"] + list(acct.keys())
umap: dict[str, list[str]] = {x: [] for x in unames}

View File

@@ -109,6 +109,7 @@ flagcats = {
"nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
"noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
"noforget": "don't forget files when deleted from disk",
"fat32": "avoid excessive reindexing on android sdcardfs",
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
"xlink": "cross-volume dupe detection / linking",
"xdev": "do not descend into other filesystems",

View File

@@ -149,6 +149,7 @@ class HttpCli(object):
self.dvol = [" "]
self.gvol = [" "]
self.upvol = [" "]
self.avol = [" "]
self.do_log = True
self.can_read = False
self.can_write = False
@@ -405,6 +406,7 @@ class HttpCli(object):
self.dvol = self.asrv.vfs.adel[self.uname]
self.gvol = self.asrv.vfs.aget[self.uname]
self.upvol = self.asrv.vfs.apget[self.uname]
self.avol = self.asrv.vfs.aadmin[self.uname]
if self.pw and (
self.pw != cookie_pw or self.conn.freshen_pwd + 30 < time.time()
@@ -3003,13 +3005,12 @@ class HttpCli(object):
def tx_mounts(self) -> bool:
suf = self.urlq({}, ["h"])
avol = [x for x in self.wvol if x in self.rvol]
rvol, wvol, avol = [
[("/" + x).rstrip("/") + "/" for x in y]
for y in [self.rvol, self.wvol, avol]
for y in [self.rvol, self.wvol, self.avol]
]
if avol and not self.args.no_rescan:
if self.avol and not self.args.no_rescan:
x = self.conn.hsrv.broker.ask("up2k.get_state")
vs = json.loads(x.get())
vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
@@ -3125,7 +3126,7 @@ class HttpCli(object):
return "" # unhandled / fallthrough
def scanvol(self) -> bool:
if not self.can_read or not self.can_write:
if not self.can_admin:
raise Pebkac(403, "not allowed for user " + self.uname)
if self.args.no_rescan:
@@ -3148,7 +3149,7 @@ class HttpCli(object):
if act != "cfg":
raise Pebkac(400, "only config files ('cfg') can be reloaded rn")
if not [x for x in self.wvol if x in self.rvol]:
if not self.avol:
raise Pebkac(403, "not allowed for user " + self.uname)
if self.args.no_reload:
@@ -3158,7 +3159,7 @@ class HttpCli(object):
return self.redirect("", "?h", x.get(), "return to", False)
def tx_stack(self) -> bool:
if not [x for x in self.wvol if x in self.rvol]:
if not self.avol and not [x for x in self.wvol if x in self.rvol]:
raise Pebkac(403, "not allowed for user " + self.uname)
if self.args.no_stack:
@@ -3862,7 +3863,8 @@ class HttpCli(object):
_ = [tagset.add(k) for k in fe["tags"]]
if icur:
taglist = [k for k in vn.flags.get("mte", "").split(",") if k in tagset]
mte = vn.flags.get("mte") or "up_ip,.up_at"
taglist = [k for k in mte.split(",") if k in tagset]
for fe in dirs:
fe["tags"] = {}
else:

View File

@@ -883,6 +883,7 @@ class Up2k(object):
rei = vol.flags.get("noidx")
reh = vol.flags.get("nohash")
n4g = bool(vol.flags.get("noforget"))
ffat = "fat32" in vol.flags
cst = bos.stat(top)
dev = cst.st_dev if vol.flags.get("xdev") else 0
@@ -919,6 +920,7 @@ class Up2k(object):
rei,
reh,
n4g,
ffat,
[],
cst,
dev,
@@ -974,6 +976,7 @@ class Up2k(object):
rei: Optional[Pattern[str]],
reh: Optional[Pattern[str]],
n4g: bool,
ffat: bool,
seen: list[str],
cst: os.stat_result,
dev: int,
@@ -1018,7 +1021,7 @@ class Up2k(object):
lmod = int(inf.st_mtime)
sz = inf.st_size
if fat32 and inf.st_mtime % 2:
if fat32 and not ffat and inf.st_mtime % 2:
fat32 = False
if stat.S_ISDIR(inf.st_mode):
@@ -1035,7 +1038,19 @@ class Up2k(object):
# self.log(" dir: {}".format(abspath))
try:
ret += self._build_dir(
db, top, excl, abspath, rap, rei, reh, n4g, seen, inf, dev, xvol
db,
top,
excl,
abspath,
rap,
rei,
reh,
n4g,
fat32,
seen,
inf,
dev,
xvol,
)
except:
t = "failed to index subdir [{}]:\n{}"

View File

@@ -127,7 +127,7 @@ window.baguetteBox = (function () {
var gallery = [];
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
var imageElementClickHandler = function (e) {
if (ctrl(e))
if (ctrl(e) || e && e.shiftKey)
return true;
e.preventDefault ? e.preventDefault() : e.returnValue = false;

View File

@@ -1230,7 +1230,8 @@ html.y #widget.open {
#wfm a.hide {
display: none;
}
#files tbody tr.fcut td {
#files tbody tr.fcut td,
#ggrid>a.fcut {
animation: fcut .5s ease-out;
}
@keyframes fcut {

View File

@@ -189,7 +189,8 @@ var Ls = {
"cl_hpick": "click one column header to hide in the table below",
"cl_hcancel": "column hiding aborted",
"ct_thumb": "in icon view, toggle icons or thumbnails$NHotkey: T",
"ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T",
"ct_csel": "use CTRL and SHIFT for file selection in grid-view",
"ct_dots": "show hidden files (if server permits)",
"ct_dir1st": "sort folders before files",
"ct_readme": "show README.md in folder listings",
@@ -651,6 +652,7 @@ var Ls = {
"cl_hcancel": "kolonne-skjuling avbrutt",
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
"ct_csel": "bruk tastene CTRL og SHIFT for markering av filer i ikonvisning",
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
"ct_dir1st": "sorter slik at mapper kommer foran filer",
"ct_readme": "vis README.md nedenfor filene",
@@ -1096,6 +1098,7 @@ ebi('op_cfg').innerHTML = (
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' +
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
' <a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '">sel</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
@@ -3689,18 +3692,27 @@ var fileman = (function () {
if (!sel.length)
toast.err(3, L.fc_emore);
var els = [];
var els = [], griden = thegrid.en;
for (var a = 0; a < sel.length; a++) {
vps.push(sel[a].vp);
if (sel.length < 100) {
els.push(ebi(sel[a].id).closest('tr'));
clmod(els[a], 'fcut');
}
if (sel.length < 100)
try {
if (griden)
els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
else
els.push(ebi(sel[a].id).closest('tr'));
clmod(els[a], 'fcut');
}
catch (ex) { }
}
setTimeout(function () {
for (var a = 0; a < els.length; a++)
clmod(els[a], 'fcut', 1);
try {
for (var a = 0; a < els.length; a++)
clmod(els[a], 'fcut', 1);
}
catch (ex) { }
}, 1);
try {
@@ -4288,7 +4300,7 @@ var thegrid = (function () {
setsz();
function gclick1(e) {
if (ctrl(e))
if (ctrl(e) && !treectl.csel && !r.sel)
return true;
return gclick.bind(this)(e, false);
@@ -4312,8 +4324,10 @@ var thegrid = (function () {
td = oth.closest('td').nextSibling,
tr = td.parentNode;
if (r.sel && !dbl) {
td.click();
if ((r.sel && !dbl && !ctrl(e)) || (treectl.csel && (e.shiftKey || ctrl(e)))) {
td.onclick.bind(td)(e);
if (e.shiftKey)
return r.loadsel();
clmod(this, 'sel', clgot(tr, 'sel'));
}
else if (widget.is_open && aplay)
@@ -4706,6 +4720,7 @@ document.onkeydown = function (e) {
if (e.shiftKey) {
clmod(el, 'sel', 't');
msel.origin_tr(el);
msel.selui();
}
@@ -4714,6 +4729,7 @@ document.onkeydown = function (e) {
}
if (k == 'Space') {
clmod(ae, 'sel', 't');
msel.origin_tr(ae);
msel.selui();
return ev(e);
}
@@ -4722,6 +4738,7 @@ document.onkeydown = function (e) {
all = msel.getall();
msel.evsel(e, sel.length < all.length);
msel.origin_id(null);
return ev(e);
}
}
@@ -5198,6 +5215,7 @@ var treectl = (function () {
bcfg_bind(r, 'ireadme', 'ireadme', true);
bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
bcfg_bind(r, 'csel', 'csel', false);
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
r.goto(get_evpath());
});
@@ -6139,6 +6157,16 @@ function apply_perms(res) {
}
function tr2id(tr) {
try {
return tr.cells[1].querySelector('a[id]').getAttribute('id');
}
catch (ex) {
return null;
}
}
function find_file_col(txt) {
var i = -1,
min = false,
@@ -6641,9 +6669,11 @@ var msel = (function () {
var r = {};
r.sel = null;
r.all = null;
r.so = null; // selection origin
r.pr = null; // previous range
r.load = function () {
if (r.sel)
r.load = function (reset) {
if (r.sel && !reset)
return;
r.sel = [];
@@ -6654,7 +6684,8 @@ var msel = (function () {
if (ao.sel)
r.sel.push(ao);
}
return;
if (!reset)
return;
}
r.all = [];
@@ -6680,6 +6711,7 @@ var msel = (function () {
};
r.loadsel = function (sel) {
r.so = r.pr = null;
r.sel = [];
r.load();
@@ -6711,15 +6743,60 @@ var msel = (function () {
thegrid.loadsel();
fileman.render();
showfile.updtree();
}
};
r.seltgl = function (e) {
ev(e);
var tr = this.parentNode;
clmod(tr, 'sel', 't');
var tr = this.parentNode,
id = tr2id(tr);
if ((treectl.csel || !thegrid.en || thegrid.sel) && e.shiftKey && r.so && id && r.so != id) {
var o1 = -1, o2 = -1;
for (a = 0; a < r.all.length; a++) {
var ai = r.all[a].id;
if (ai == r.so)
o1 = a;
if (ai == id)
o2 = a;
}
var st = r.all[o1].sel;
if (o1 > o2)
o2 = [o1, o1 = o2][0];
if (r.pr) {
// invert previous range, in case it was narrowed
for (var a = r.pr[0]; a <= r.pr[1]; a++)
clmod(ebi(r.all[a].id).closest('tr'), 'sel', !st);
// and invert current selection if repeated
if (r.pr[0] === o1 && r.pr[1] === o2)
st = !st;
}
for (var a = o1; a <= o2; a++)
clmod(ebi(r.all[a].id).closest('tr'), 'sel', st);
r.pr = [o1, o2];
if (window.getSelection)
window.getSelection().removeAllRanges();
}
else {
clmod(tr, 'sel', 't');
r.origin_tr(tr);
}
r.selui();
}
};
r.origin_tr = function (tr) {
r.so = tr2id(tr);
r.pr = null;
};
r.origin_id = function (id) {
r.so = id;
r.pr = null;
};
r.evsel = function (e, fun) {
ev(e);
r.so = r.pr = null;
var trs = QSA('#files tbody tr');
for (var a = 0, aa = trs.length; a < aa; a++)
clmod(trs[a], 'sel', fun);
@@ -7344,7 +7421,7 @@ ebi('path').onclick = function (e) {
ebi('files').onclick = ebi('docul').onclick = function (e) {
if (ctrl(e))
if (!treectl.csel && e && (ctrl(e) || e.shiftKey))
return true;
var tgt = e.target.closest('a[id]');
@@ -7441,6 +7518,8 @@ function reload_browser() {
reload_mp();
try { showsort(ftab); } catch (ex) { }
makeSortable(ftab, function () {
msel.origin_id(null);
msel.load(true);
thegrid.setdirty();
mp.read_order();
});

View File

@@ -1,3 +1,69 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0714-1558 `v1.8.2` URGENT: fix path traversal vulnerability
* read-only demo server at https://a.ocv.me/pub/demo/
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) [client testbed](https://cd.ocv.me/b/)
Starting with the bad and important news; this release fixes https://github.com/9001/copyparty/security/advisories/GHSA-pxfv-7rr3-2qjg / [CVE-2023-37474](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-37474) -- so please upgrade!
Every version until now had a [path traversal vulnerability](https://owasp.org/www-community/attacks/Path_Traversal) which allowed read-access to any file on the server's filesystem. To summarize,
* Every file that the copyparty process had the OS-level permissions to read, could be retrieved over HTTP without password authentication
* However, an attacker would need to know the full (or copyparty-module-relative) path to the file; it was luckily impossible to list directory contents to discover files on the server
* You may have been running copyparty with some mitigations against this:
* [prisonparty](https://github.com/9001/copyparty/tree/hovudstraum/bin#prisonpartysh) limited the scope of access to files which were intentionally given to copyparty for sharing; meaning all volumes, as well as the following read-only filesystem locations: `/bin`, `/lib`, `/lib32`, `/lib64`, `/sbin`, `/usr`, `/etc/alternatives`
* the [nix package](https://github.com/9001/copyparty#nix-package) has a similar mitigation implemented using systemd concepts
* [docker containers](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) would only expose the files which were intentionally mounted into the container, so even better
* More conventional setups, such as just running the sfx (python or exe editions), would unfortunately expose all files readable by the current user
* The following configurations would have made the impact much worse:
* running copyparty as root
So, three years, and finally a CVE -- which has been there since day one... Not great huh. There is a list of all the copyparty alternatives that I know of in the `similar software` link above.
Thanks for flying copyparty! And especially if you decide to continue doing so :-)
## new features
* #43 volflags to specify thumbnailer behavior per-volume;
* `--th-no-crop` / volflag `nocrop` to specify whether autocrop should be disabled
* `--th-size` / volflag `thsize` to set a custom thumbnail resolution
* `--th-convt` / volflag `convt` to specify conversion timeout
* #45 resulted in a handful of opportunities to tighten security in intentionally-dangerous setups (public folders with anonymous uploads enabled):
* a new permission, `a` (in addition to the existing `rwmdgG`), to show the uploader-IP and upload-time for each file in the file listing
* accidentally incompatible with the `d2t` volflag (will be fixed in the next ver)
* volflag `nohtml` is a good defense against (un)intentional XSS; it returns HTML-files and markdown-files as plaintext instead of rendering them, meaning any malicious `<script>` won't run -- bad idea for regular use since it breaks fundamental functionality, but good when you really need it
* the README-previews below the file-listing still renders as usual, as this is fine thanks to the sandbox
* a new eventhook `--xban` to run a plugin when copyparty decides to ban someone (for password bruteforcing or excessive 404's), for example to blackhole the IP using fail2ban or similar
## bugfixes
* **fixes a path traversal vulnerability,** https://github.com/9001/copyparty/security/advisories/GHSA-pxfv-7rr3-2qjg / [CVE-2023-37474](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-37474)
* HUGE thanks to @TheHackyDog for reporting this !!
* if you use a reverse proxy, you can check if you have been exploited like so:
* nginx: grep your logs for URLs containing both `.cpr/` and `%2[^0]`, for example using the following command:
```bash
(gzip -dc access.log.*.gz; cat access.log) | sed -r 's/" [0-9]+ .*//' | grep -E 'cpr/.*%2[^0]' | grep -vF data:image/svg
```
* 77f1e5144455eb946db7368792ea11c934f0f6da fixes an extremely unlikely race-condition (see the commit for details)
* 8f59afb1593a75b8ce8c91ceee304097a07aea6e fixes another race-condition which is a bit worse:
* the unpost feature could collide with other database activity, with the worst-case outcome being aborted batch operations, for example a directory move or a batch-rename which stops halfways
----
# 💾 what to download?
| download link | is it good? | description |
| -- | -- | -- |
| **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** | ✅ the best 👍 | runs anywhere! only needs python |
| [a docker image](https://github.com/9001/copyparty/blob/hovudstraum/scripts/docker/README.md) | it's ok | good if you prefer docker 🐋 |
| [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) | ⚠️ [acceptable](https://github.com/9001/copyparty#copypartyexe) | for [win8](https://user-images.githubusercontent.com/241032/221445946-1e328e56-8c5b-44a9-8b9f-dee84d942535.png) or later; built-in thumbnailer |
| [u2c.exe](https://github.com/9001/copyparty/releases/download/v1.7.1/u2c.exe) | ⚠️ acceptable | [CLI uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) as a win7+ exe ([video](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm)) |
| [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) | ⛔️ [dangerous](https://github.com/9001/copyparty#copypartyexe) | for [win7](https://user-images.githubusercontent.com/241032/221445944-ae85d1f4-d351-4837-b130-82cab57d6cca.png) -- never expose to the internet! |
| [cpp-winpe64.exe](https://github.com/9001/copyparty/releases/download/v1.8.2/copyparty-winpe64.exe) | ⛔️ dangerous | runs on [64bit WinPE](https://user-images.githubusercontent.com/241032/205454984-e6b550df-3c49-486d-9267-1614078dd0dd.png), otherwise useless |
* except for [u2c.exe](https://github.com/9001/copyparty/releases/download/v1.7.1/u2c.exe), all of the options above are equivalent
* the zip and tar.gz files below are just source code
* python packages are available at [PyPI](https://pypi.org/project/copyparty/#files)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0707-2220 `v1.8.1` in case of 404

View File

@@ -77,8 +77,3 @@ or using commandline arguments,
# build the images yourself
basically `./make.sh hclean pull img push` but see [devnotes.md](./devnotes.md)
# notes
* currently unable to play [tracker music](https://en.wikipedia.org/wiki/Module_file) (mod/s3m/xm/it/...) -- will be fixed in june 2023 (Alpine 3.18)

73
scripts/test/ptrav.py Executable file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import re
import sys
import time
import itertools
import requests
atlas = ["%", "25", "2e", "2f", ".", "/"]
def genlen(ubase, port, ntot, nth, wlen):
n = 0
t0 = time.time()
print("genlen %s nth %s port %s" % (wlen, nth, port))
rsession = requests.Session()
ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/")
for path in itertools.product(atlas, repeat=wlen):
if "%" not in path:
continue
path = "".join(path)
if ptn.search(path):
continue
n += 1
if n % ntot != nth:
continue
url = ubase % (port, path)
if n % 500 == nth:
spd = n / (time.time() - t0)
print(wlen, n, int(spd), url)
try:
r = rsession.get(url)
except KeyboardInterrupt:
raise
except:
print("\n[=== RETRY ===]", url)
try:
r = rsession.get(url)
except:
r = rsession.get(url)
if "fgsfds" in r.text:
with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f:
f.write(url)
raise Exception("HIT! {}".format(url))
def main():
ubase = sys.argv[1]
port = int(sys.argv[2])
ntot = int(sys.argv[3])
nth = int(sys.argv[4])
for wlen in range(20):
genlen(ubase, port, ntot, nth, wlen)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
"""
python3 -m copyparty -v srv::r -p 3931 -q -j4
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 0
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 1
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 2
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 0
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 1
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 2
(13x slower than /tests/ptrav.py)
"""

View File

@@ -62,7 +62,16 @@ class Cpp(object):
def tc1(vflags):
ub = "http://127.0.0.1:4321/"
td = os.path.join("srv", "smoketest")
try:
if not os.path.exists("/dev/shm"):
raise Exception()
td = "/dev/shm/cppsmoketst"
ntd = 4
except:
td = os.path.join("srv", "smoketest")
ntd = 2
try:
shutil.rmtree(td)
except:
@@ -91,6 +100,7 @@ def tc1(vflags):
"-p4321",
"-e2dsa",
"-e2tsr",
"--dbd=yolo",
"--no-mutagen",
"--th-ff-jpg",
"--hist",
@@ -99,38 +109,38 @@ def tc1(vflags):
pdirs = []
hpaths = {}
for d1 in ["r", "w", "a"]:
for d1 in ["r", "w", "rw"]:
pdirs.append("{}/{}".format(td, d1))
pdirs.append("{}/{}/j".format(td, d1))
for d2 in ["r", "w", "a", "c"]:
for d2 in ["r", "w", "rw", "c"]:
d = os.path.join(td, d1, "j", d2)
pdirs.append(d)
os.makedirs(d)
pdirs = [x.replace("\\", "/") for x in pdirs]
udirs = [x.split("/", 2)[2] for x in pdirs]
udirs = [x.split("/", ntd)[ntd] for x in pdirs]
perms = [x.rstrip("cj/")[-1] for x in pdirs]
perms = ["rw" if x == "a" else x for x in perms]
for pd, ud, p in zip(pdirs, udirs, perms):
if ud[-1] == "j" or ud[-1] == "c":
continue
hp = None
if pd.endswith("st/a"):
if pd.endswith("st/rw"):
hp = hpaths[ud] = os.path.join(td, "db1")
elif pd[:-1].endswith("a/j/"):
elif pd[:-1].endswith("rw/j/"):
hpaths[ud] = os.path.join(td, "dbm")
hp = None
else:
hp = "-"
hpaths[ud] = os.path.join(pd, ".hist")
arg = "{}:{}:{}".format(pd, ud, p)
arg = "{}:{}:a{}".format(pd, ud, p)
if hp:
arg += ":c,hist=" + hp
args += ["-v", arg + vflags]
# print("\n".join(args))
# return
cpp = Cpp(args)
CPP.append(cpp)
@@ -163,7 +173,7 @@ def tc1(vflags):
# stat filesystem
for d, p in zip(pdirs, perms):
u = "{}/{}.h264".format(d, d.split("test/")[-1].replace("/", ""))
u = "{}/{}.h264".format(d, d[len(td) :].replace("/", ""))
ok = os.path.exists(u)
if ok != (p in ["rw", "w"]):
raise Exception("stat {} with perm {} at {}".format(ok, p, u))

87
tests/ptrav.py Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
import re
import sys
import time
import itertools
from . import util as tu
from .util import Cfg
from copyparty.authsrv import AuthSrv
from copyparty.httpcli import HttpCli
atlas = ["%", "25", "2e", "2f", ".", "/"]
def nolog(*a, **ka):
pass
def hdr(query):
h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n"
return h.format(query).encode("utf-8")
def curl(args, asrv, url, binary=False):
conn = tu.VHttpConn(args, asrv, nolog, hdr(url))
HttpCli(conn).run()
if binary:
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
return [h.decode("utf-8"), b]
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
def genlen(ubase, ntot, nth, wlen):
args = Cfg(v=["s2::r"], a=["o:o", "x:x"])
asrv = AuthSrv(args, print)
# h, ret = curl(args, asrv, "hey")
n = 0
t0 = time.time()
print("genlen %s nth %s" % (wlen, nth))
ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/")
for path in itertools.product(atlas, repeat=wlen):
if "%" not in path:
continue
path = "".join(path)
if ptn.search(path):
continue
n += 1
if n % ntot != nth:
continue
url = ubase + path + "fa"
if n % 500 == nth:
spd = n / (time.time() - t0)
print(wlen, n, int(spd), url)
hdr, r = curl(args, asrv, url)
if "fgsfds" in r:
with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f:
f.write(url)
raise Exception("HIT! {}".format(url))
def main():
ubase = sys.argv[1]
ntot = int(sys.argv[2])
nth = int(sys.argv[3])
for wlen in range(20):
genlen(ubase, ntot, nth, wlen)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
"""
nice pypy3 -m tests.ptrav "" 2 0
nice pypy3 -m tests.ptrav "" 2 1
nice pypy3 -m tests.ptrav .cpr 2 0
nice pypy3 -m tests.ptrav .cpr 2 1
(13x faster than /scripts/test/ptrav.py)
"""

View File

@@ -32,7 +32,7 @@ if MACOS:
from copyparty.__init__ import E
from copyparty.__main__ import init_E
from copyparty.util import Unrecv, FHC
from copyparty.util import Unrecv, FHC, Garda
init_E(E)
@@ -98,7 +98,7 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None):
ka = {}
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vc xdev xlink xvol"
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vague_403 vc ver xdev xlink xvol"
ka.update(**{k: False for k in ex.split()})
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
@@ -113,7 +113,7 @@ class Cfg(Namespace):
ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo"
ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth textfiles unlist R RS SR"
ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth name textfiles unlist R RS SR"
ka.update(**{k: "" for k in ex.split()})
ex = "on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
@@ -176,6 +176,9 @@ class VHttpSrv(object):
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
self.j2 = {x: J2_FILES for x in aliases}
self.gpwd = Garda("")
self.g404 = Garda("")
def cachebuster(self):
return "a"