Compare commits

...

39 Commits

Author SHA1 Message Date
ed
4accef00fb v1.16.16 2025-02-28 18:46:32 +00:00
ed
d779525500 move -volflag warning to avoid false positives 2025-02-28 18:13:23 +00:00
ed
65a7706f77 add helptext for volflags dk, dks, dky 2025-02-28 17:56:51 +00:00
ed
5e12abbb9b ignore impossible lastmod on upload; closes #142
android-chrome bug https://issues.chromium.org/issues/393149335
sends last-modified time `-11644473600` for all uploads

this has been fixed in chromium, but there might be similar
bugs in other browsers, so add server-side and client-side
detection for unreasonable lastmod times

previously, if the js detected a similar situation, it would
substitute the lastmod-time with the client's wallclock, but
now the server's wallclock is always preferrred as fallback
2025-02-28 17:48:14 +00:00
daimond113
e0fe2b97be nix: add mainProgram
Silences warnings like "getExe: Package
"copyparty-1.16.15" does not have the
meta.mainProgram attribute. We'll assume that the
main program has the same name for now, but this
behavior is deprecated, because it leads to
surprising errors when the assumption does not
hold. If the package has a main program, please
set `meta.mainProgram` in its definition to make
this warning go away. Otherwise, if the package
does not have a main program, or if you don't
control its definition, use getExe' to specify
the name to the program, such as lib.getExe' foo
"bar"."
2025-02-26 23:07:19 +01:00
ed
bd33863f9f update pkgs to 1.16.15 2025-02-25 01:25:15 +00:00
ed
a011139894 v1.16.15 2025-02-25 00:17:58 +00:00
ed
36866f1d36 dangit.wav 2025-02-25 00:11:57 +00:00
ed
407531bcb1 fix markdown / text-editor jank
* only indicate file-history for markdown files since
   other files won't load into the editor which makes
   that entirely pointless; do file extension instead

* text-editor: in files containing one single line,
   ^C followed by ^V ^Z would accidentally a letter

and fix unhydrated extensions
2025-02-25 00:03:22 +00:00
ed
3adbb2ff41 https://youtu.be/WyXebd3I3Vo 2025-02-24 23:32:03 +00:00
ed
499ae1c7a1 other minor html-escaping fixes
mostly related to error-handling for uploads, network-loss etc,
nothing worse than the dom-xss just now
2025-02-24 22:42:05 +00:00
ed
438ea6ccb0 fix GHSA-m2jw-cj8v-937r ;
this fixes a DOM-Based XSS when preparing files for upload;
empty files would have their filenames rendered as HTML in
a messagebox, making it possible to trick users into running
arbitrary javascript by giving them maliciously-named files

note that, being a general-purpose webserver, it is still
intentionally possible to upload and execute arbitrary
javascript, just not in this unexpected manner
2025-02-24 21:23:13 +00:00
ed
598a29a733 mention sony psp support (thx dwarf) 2025-02-23 21:37:21 +00:00
ed
6d102fc826 mention risc-v support 2025-02-20 04:51:04 +00:00
ed
fca07fbb62 update pkgs to 1.16.14 2025-02-19 23:35:05 +00:00
ed
cdedcc24b8 v1.16.14 2025-02-19 23:09:14 +00:00
ed
60d5f27140 new example: randpic.py 2025-02-19 22:41:30 +00:00
ed
cb413bae49 webdav: a healthy dash of paranoia
there's probably at least one client sending `Overwrite: False`
instead of the spec-correct `Overwrite: F`
2025-02-19 22:07:26 +00:00
ed
e9f78ea70c up2k: tristate option for overwriting files; closes #139
adds a third possible value for the `replace` property in handshakes:

* absent or False: never overwrite an existing file on the server,
   and instead generate a new filename to avoid collision

* True: always overwrite existing files on the server

* "mt": only overwrite if client's last-modified is more recent
   (this is the new option)

the new UI button toggles between all three options,
defaulting to never-overwrite
2025-02-19 21:58:56 +00:00
ed
6858cb066f spinner: themes + improve positioning
loading-spinner is either `#dlt_t` or `#dlt_f`
(tree or files), appearing top-left or top-right,
regardless of page/tree scroll (position:fixed)
2025-02-19 18:55:33 +00:00
ed
4be0d426f4 option to forget uploader-IP from db after some time
does this mean copyparty is GDPR-compliant now? idklol
2025-02-17 23:47:59 +00:00
ed
7d7d5d6c3c fix custom spinner css on initial page load 2025-02-17 23:26:21 +00:00
ed
0422387e90 readme: changing the loading spinner (#138) 2025-02-16 19:28:57 +00:00
ed
2ed5fd9ac4 readme: diagnosing broken thumbnails (#137) 2025-02-16 19:22:17 +00:00
ed
2beb2acc24 readme: permanent cloudflare tunnel (#137) 2025-02-16 18:59:18 +00:00
ed
56ce591908 synology dsm: add updating 2025-02-16 18:12:35 +00:00
ed
b190e676b4 fix cosmetic volflag stuff:
* `xz` would show the "unrecognized volflag" warning,
   but it still applied correctly

* removing volflags with `-foo` would also show the warning
   but it would still get removed correctly

* hide `ext_th_d` in the startup volume-listing
2025-02-14 20:54:13 +00:00
ed
19520b2ec9 remove patch for musl cve (no longer necessary) 2025-02-14 09:15:52 +00:00
ed
eeb96ae8b5 update pkgs to 1.16.13 2025-02-13 21:43:32 +00:00
ed
cddedd37d5 v1.16.13 2025-02-13 20:57:04 +00:00
ed
4d6626b099 workaround musl 1.2.5 cve 2025-02-13 20:53:47 +00:00
ed
7a55833bb2 silence linter 2025-02-13 18:34:41 +00:00
ed
7e4702cf09 file-extension icons
global-option / volflag `ext_th` specifies
custom thumbnail for a given file extension
2025-02-13 18:32:01 +00:00
ed
685f08697a alternative loader spinners 2025-02-13 17:07:48 +00:00
ed
a255db706d make volflags less confusing
1. warn about unrecognized volflags

previously, when specifying an unknown volflag, it would
be silently ignored, giving the impression that it applied

2. also allow uppercase, kebab-case
    (previously, only snake_case was accepted)

3. mention every volflag in --help-flags
    (some volflags were missing)
2025-02-13 00:34:46 +00:00
ed
9d76902710 WebDAV: adjust 401-mask for GETs (#136)
some clients, including KDE Dolphin (kioworker/6.10) keeps
sending requests without the basic-auth header, expecting
the server to respond with a 401 before it does

most clients only do this for the initial request, which is
usually a PROPFIND, which makes this nice and simple -- but
turns out we need to consider this for GET as well...

this is tricky because a graphical webbrowser must never
receive a 401 lest it becomes near-impossible to deauth,
and that's exactly what Dolphin pretends to be in its UA

man ( ´_ゝ`)

note: `KIO/` hits konqueror so don't
2025-02-11 23:32:44 +00:00
ed
62ee7f6980 WebDAV: support COPY, KDE-Dolphin (#136):
* add support for the COPY verb

* COPY/MOVE: add overwrite support;
   default is True according to rfc
   (only applies to single files for now)

* COPY/MOVE/MKCOL: return 401 as necessary
   for clients which rechallenge frequently
   such as KDE Dolphin (KIO/6.10)

* MOVE: support webdav:// Destination prefix
   as used by KDE Dolphin (KIO/6.10)

* MOVE: vproxy support
2025-02-11 21:34:24 +00:00
ed
2f6707825a improve usb-eject hook:
* don't crash internet explorer
* support running as root
* support old linuxen
2025-02-10 04:52:57 +00:00
ed
7dda77dcb4 update pkgs to 1.16.12 2025-02-10 00:04:04 +00:00
26 changed files with 722 additions and 143 deletions

View File

@@ -94,9 +94,11 @@ turn almost any device into a file server with resumable uploads/downloads using
* [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
* [real-ip](#real-ip) - teaching copyparty how to see client IPs
* [reverse-proxy performance](#reverse-proxy-performance)
* [permanent cloudflare tunnel](#permanent-cloudflare-tunnel) - if you have a domain and want to get your copyparty online real quick
* [prometheus](#prometheus) - metrics/stats can be enabled
* [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
* [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
* [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)
@@ -159,8 +161,8 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
* **MacOS:** `port install py-Pillow ffmpeg`
* **MacOS** (alternative): `brew install pillow ffmpeg`
* **Windows:** `python -m pip install --user -U Pillow`
* install python and ffmpeg manually; do not use `winget` or `Microsoft Store` (it breaks $PATH)
* copyparty.exe comes with `Pillow` and only needs `ffmpeg`
* install [python](https://www.python.org/downloads/windows/) and [ffmpeg](#optional-dependencies) manually; do not use `winget` or `Microsoft Store` (it breaks $PATH)
* copyparty.exe comes with `Pillow` and only needs [ffmpeg](#optional-dependencies) for mediatags/videothumbs
* see [optional dependencies](#optional-dependencies) to enable even more features
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)
@@ -183,6 +185,8 @@ first download [cloudflared](https://developers.cloudflare.com/cloudflare-one/co
as the tunnel starts, it will show a URL which you can share to let anyone browse your stash or upload files to you
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)
since people will be connecting through cloudflare, run copyparty with `--xff-hdr cf-connecting-ip` to detect client IPs correctly
@@ -224,6 +228,7 @@ also see [comparison to similar software](./docs/versus.md)
* ☑ [upnp / zeroconf / mdns / ssdp](#zeroconf)
* ☑ [event hooks](#event-hooks) / script runner
* ☑ [reverse-proxy support](https://github.com/9001/copyparty#reverse-proxy)
* ☑ cross-platform (Windows, Linux, Macos, Android, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
* upload
* ☑ basic: plain multipart, ie6 support
* ☑ [up2k](#uploading): js, resumable, multithreaded
@@ -401,6 +406,9 @@ upgrade notes
"frequently" asked questions
* can I change the 🌲 spinning pine-tree loading animation?
* [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
* is it possible to block read-access to folders unless you know the exact URL for a particular file inside?
* yes, using the [`g` permission](#accounts-and-volumes), see the examples there
* 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
@@ -423,6 +431,14 @@ upgrade notes
* copyparty seems to think I am using http, even though the URL is https
* 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
* thumbnails are broken (you get a colorful square which says the filetype instead)
* you need to install `FFmpeg` or `Pillow`; see [thumbnails](#thumbnails)
* thumbnails are broken (some images appear, but other files just get a blank box, and/or the broken-image placeholder)
* probably due to a reverse-proxy messing with the request URLs and stripping the query parameters (`?th=w`), so check your URL rewrite rules
* 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
* could also be due to misbehaving privacy-related browser extensions, so try to disable those
* i want to learn python and/or programming and am considering looking at the copyparty source code in that occasion
* ```bash
_| _ __ _ _|_
@@ -653,6 +669,7 @@ press `g` or `田` to toggle grid-view instead of the file listing and `t` togg
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
* pyvips is 3x faster than Pillow, Pillow is 3x faster than FFmpeg
* disable thumbnails for specific volumes with volflag `dthumb` for all, or `dvthumb` / `dathumb` / `dithumb` for video/audio/images only
* for installing FFmpeg on windows, see [optional dependencies](#optional-dependencies)
audio files are converted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
@@ -664,6 +681,8 @@ enabling `multiselect` lets you click files to select them, and then shift-click
* `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
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
to show `/icons/exe.png` as the thumbnail for all .exe files, `--ext-th=exe=/icons/exe.png` (optionally as a volflag)
config file example:
```yaml
@@ -678,6 +697,7 @@ config file example:
flags:
dthumb # disable ALL thumbnails and audio transcoding
dvthumb # only disable video thumbnails
ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
```
@@ -761,8 +781,11 @@ the up2k UI is the epitome of polished intuitive experiences:
* "parallel uploads" specifies how many chunks to upload at the same time
* `[🏃]` analysis of other files should continue while one is uploading
* `[🥔]` shows a simpler UI for faster uploads from slow devices
* `[🛡️]` decides when to overwrite existing files on the server
* `🛡️` = never (generate a new filename instead)
* `🕒` = overwrite if the server-file is older
* `♻️` = always overwrite if the files are different
* `[🎲]` generate random filenames during upload
* `[📅]` preserve last-modified timestamps; server times will match yours
* `[🔎]` switch between upload and [file-search](#file-search) mode
* ignore `[🔎]` if you add files by dragging them into the browser
@@ -1979,6 +2002,26 @@ in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds
* if these results are bullshit because my config exampels are bad, please submit corrections!
## permanent cloudflare tunnel
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")
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:
* `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`
* 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`
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
config file example:
```yaml
[global]
xff-hdr: cf-connecting-ip
```
## prometheus
metrics/stats can be enabled at URL `/.cpr/metrics` for grafana / prometheus / etc (openmetrics 1.0.0)
@@ -2065,6 +2108,18 @@ in a config file, this is the same as:
run copyparty with `--mimes` to list all the default mappings
### GDPR compliance
imagine using copyparty professionally... **TINLA/IANAL; EU laws are hella confusing**
* remember to disable logging, or configure logrotation to an acceptable timeframe with `-lo cpp-%Y-%m%d.txt.xz` or similar
* if running with the database enabled (recommended), then have it forget uploader-IPs after some time using `--forget-ip 43200`
* don't set it too low; [unposting](#unpost) a file is no longer possible after this takes effect
* if you actually *are* a lawyer then I'm open for feedback, would be fun
### feature chickenbits
buggy feature? rip it out by setting any of the following environment variables to disable its associated bell or whistle,
@@ -2252,6 +2307,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
| **ie4** and **netscape** 4.0 | can browse, upload with `?b=u`, auth with `&pw=wark` |
| **ncsa mosaic** 2.7 | does not get a pass, [pic1](https://user-images.githubusercontent.com/241032/174189227-ae816026-cf6f-4be5-a26e-1b3b072c1b2f.png) - [pic2](https://user-images.githubusercontent.com/241032/174189225-5651c059-5152-46e9-ac26-7e98e497901b.png) |
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
| **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
| **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
<p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
@@ -2582,6 +2638,8 @@ enable [smb](#smb-server) support (**not** recommended): `impacket==0.12.0`
`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`
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%`
### dependency chickenbits

View File

@@ -21,6 +21,7 @@ each plugin must define a `main()` which takes 3 arguments;
## on404
* [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file
* [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/`
* [sorry.py](answer.py) replies with a custom message instead of the usual 404
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary

35
bin/handlers/randpic.py Normal file
View File

@@ -0,0 +1,35 @@
import os
import random
from urllib.parse import quote
# assuming /foo/bar/ is a valid URL but /foo/bar/randpic.png does not exist,
# hijack the 404 with a redirect to a random pic in that folder
#
# thx to lia & kipu for the idea
def main(cli, vn, rem):
req_fn = rem.split("/")[-1]
if not cli.can_read or not req_fn.startswith("randpic"):
return
req_abspath = vn.canonical(rem)
req_ap_dir = os.path.dirname(req_abspath)
files_in_dir = os.listdir(req_ap_dir)
if "." in req_fn:
file_ext = "." + req_fn.split(".")[-1]
files_in_dir = [x for x in files_in_dir if x.lower().endswith(file_ext)]
if not files_in_dir:
return
selected_file = random.choice(files_in_dir)
req_url = "/".join([vn.vpath, rem]).strip("/")
req_dir = req_url.rsplit("/", 1)[0]
new_url = "/".join([req_dir, quote(selected_file)]).strip("/")
cli.reply(b"redirecting...", 302, headers={"Location": "/" + new_url})
return "true"

View File

@@ -9,7 +9,7 @@ function eject_cb() {
if (t.indexOf('can be safely unplugged') < 0 && t.indexOf('Device can be removed') < 0)
return toast.err(30, 'usb eject failed:\n\n' + t);
toast.ok(5, esc(t.replace(/ - /g, '\n\n')));
toast.ok(5, esc(t.replace(/ - /g, '\n\n')).trim());
usbclick(); setTimeout(usbclick, 10);
};
@@ -19,12 +19,14 @@ function add_eject_2(a) {
return;
var v = aw[2],
k = 'umount_' + v;
k = 'umount_' + v,
o = ebi(k);
if (o)
o.parentNode.removeChild(o);
qsr('#' + k);
a.appendChild(mknod('span', k, '⏏'), a);
var o = ebi(k);
o = ebi(k);
o.style.cssText = 'position:absolute; right:1em; margin-top:-.2em; font-size:1.3em';
o.onclick = function (e) {
ev(e);
@@ -38,8 +40,9 @@ function add_eject_2(a) {
};
function add_eject() {
for (var a of QSA('#treeul a[href^="/usb/"]'))
add_eject_2(a);
var o = QSA('#treeul a[href^="/usb/"]');
for (var a = o.length - 1; a > 0; a--)
add_eject_2(o[a]);
};
(function() {

View File

@@ -14,13 +14,13 @@ remove those flashdrives, then boy howdy are you in the right place :D
put usb-eject.js in the webroot (or somewhere else http-accessible)
then run copyparty with these args:
-v /run/media/ed:/usb:A:c,hist=/tmp/junk
-v /run/media/egon:/usb:A:c,hist=/tmp/junk
--xm=c1,bin/hooks/usb-eject.py
--js-browser=/usb-eject.js
which does the following respectively,
* share all of /run/media/ed as /usb with admin for everyone
* share all of /run/media/egon as /usb with admin for everyone
and put the histpath somewhere it won't cause trouble
* run the usb-eject hook with stdout redirect to the web-ui
* add the complementary usb-eject.js to the browser
@@ -31,15 +31,24 @@ which does the following respectively,
def main():
try:
label = sys.argv[1].split(":usb-eject:")[1].split(":")[0]
mp = "/run/media/ed/" + label
mp = "/run/media/egon/" + label
# print("ejecting [%s]... " % (mp,), end="")
mp = os.path.abspath(os.path.realpath(mp.encode("utf-8")))
st = os.lstat(mp)
if not stat.S_ISDIR(st.st_mode):
raise Exception("not a regular directory")
cmd = [b"gio", b"mount", b"-e", mp]
print(sp.check_output(cmd).decode("utf-8", "replace").strip())
# if you're running copyparty as root (thx for the faith)
# you'll need something like this to make dbus talkative
cmd = b"sudo -u egon DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus gio mount -e"
# but if copyparty and the ui-session is running
# as the same user (good) then this is plenty
cmd = b"gio mount -e"
cmd = cmd.split(b" ") + [mp]
ret = sp.check_output(cmd).decode("utf-8", "replace")
print(ret.strip() or (label + " can be safely unplugged"))
except Exception as ex:
print("unmount failed: %r" % (ex,))

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
S_VERSION = "2.9"
S_BUILD_DT = "2025-01-27"
S_VERSION = "2.10"
S_BUILD_DT = "2025-02-19"
"""
u2c.py: upload to copyparty
@@ -807,7 +807,9 @@ def handshake(ar, file, search):
else:
if ar.touch:
req["umod"] = True
if ar.ow:
if ar.owo:
req["replace"] = "mt"
elif ar.ow:
req["replace"] = True
file.recheck = False
@@ -1538,6 +1540,7 @@ source file/folder selection uses rsync syntax, meaning that:
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
ap.add_argument("--owo", action="store_true", help="overwrite existing files if server-file is older")
ap.add_argument("--spd", action="store_true", help="print speeds for each file")
ap.add_argument("--version", action="store_true", help="show version and exit")

View File

@@ -1,6 +1,6 @@
# Maintainer: icxes <dev.null@need.moe>
pkgname=copyparty
pkgver="1.16.11"
pkgver="1.16.15"
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=("c8b594e9ca22d81a316ee8b10a9f5f4ebb11f0e4767738e0cb1b1704b13b2d70")
sha256sums=("080844a7458073c86714dde5fa659a64120909568005420ff81a27e353e91a72")
build() {
cd "${srcdir}/${pkgname}-${pkgver}"

View File

@@ -64,4 +64,5 @@ in stdenv.mkDerivation {
--set PATH '${lib.makeBinPath ([ utillinux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \
--add-flags "$out/share/copyparty-sfx.py"
'';
meta.mainProgram = "copyparty";
}

View File

@@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.16.11/copyparty-sfx.py",
"version": "1.16.11",
"hash": "sha256-Zl8f6mzWamwDeTnL5T5i7NdgIqYHy601oEWw8JPrPrI="
"url": "https://github.com/9001/copyparty/releases/download/v1.16.15/copyparty-sfx.py",
"version": "1.16.15",
"hash": "sha256-vpI/N01pCeCiAv3haE1Zac1X9Oyp2vqPAC1WEcl5phA="
}

View File

@@ -1039,6 +1039,7 @@ def add_upload(ap):
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")
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)")
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]")
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)")
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")
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
@@ -1183,6 +1184,7 @@ def add_webdav(ap):
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)")
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)")
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)")
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")
def add_tftp(ap):
@@ -1268,7 +1270,7 @@ def add_optouts(ap):
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")
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
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)")
def add_safety(ap):
@@ -1395,7 +1397,7 @@ def add_transcoding(ap):
def add_rss(ap):
ap2 = ap.add_argument_group('RSS options')
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental)")
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
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")
@@ -1418,6 +1420,7 @@ def add_db_general(ap, hcores):
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")
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)")
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)")
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)")
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)")
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)")
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")
@@ -1486,7 +1489,9 @@ def add_ui(ap, retry):
ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
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)")
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")
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)")
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])")
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]")
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")

View File

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

View File

@@ -1289,10 +1289,10 @@ class AuthSrv(object):
# one or more bools before the final flag; eat them
n1, uname = uname.split(",", 1)
for _, vp, _, _ in vols:
self._read_volflag(flags[vp], n1, True, False)
self._read_volflag(vp, flags[vp], n1, True, False)
for _, vp, _, _ in vols:
self._read_volflag(flags[vp], uname, cval, False)
self._read_volflag(vp, flags[vp], uname, cval, False)
return
@@ -1379,20 +1379,42 @@ class AuthSrv(object):
def _read_volflag(
self,
vpath: str,
flags: dict[str, Any],
name: str,
value: Union[str, bool, list[str]],
is_list: bool,
) -> None:
if name not in flagdescs:
name = name.lower()
# volflags are snake_case, but a leading dash is the removal operator
stripped = name.lstrip("-")
zi = len(name) - len(stripped)
if zi > 1:
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]"
self.log(t % (vpath, name, stripped), 3)
name = stripped
zi = 0
if stripped not in flagdescs and "-" in stripped:
name = ("-" * zi) + stripped.replace("-", "_")
desc = flagdescs.get(name.lstrip("-"), "?").replace("\n", " ")
if not name:
self._e("└─unreadable-line")
t = "WARNING: the config for volume [/%s] indicated that a volflag was to be defined, but the volflag name was blank"
self.log(t % (vpath,), 3)
return
if re.match("^-[^-]+$", name):
t = "└─unset volflag [{}] ({})"
self._e(t.format(name[1:], desc))
flags[name] = True
return
zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
if name not in zs.split():
if value is True:
t = "└─add volflag [{}] = {} ({})"
@@ -1557,6 +1579,18 @@ class AuthSrv(object):
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
vol.root = vfs
zs = "neversymlink"
k_ign = set(zs.split())
for vol in vfs.all_vols.values():
unknown_flags = set()
for k, v in vol.flags.items():
ks = k.lstrip("-")
if ks not in flagdescs and ks not in k_ign:
unknown_flags.add(k)
if unknown_flags:
t = "WARNING: the config for volume [/%s] has unrecognized volflags; will ignore: '%s'"
self.log(t % (vol.vpath, "', '".join(unknown_flags)), 3)
enshare = self.args.shr
shr = enshare[1:-1]
shrs = enshare[1:]
@@ -1918,11 +1952,8 @@ class AuthSrv(object):
if vf not in vol.flags:
vol.flags[vf] = getattr(self.args, ga)
for k in ("nrand",):
if k not in vol.flags:
vol.flags[k] = getattr(self.args, k)
for k in ("nrand", "u2abort", "ups_who", "zip_who"):
zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
for k in zs.split():
if k in vol.flags:
vol.flags[k] = int(vol.flags[k])
@@ -1974,8 +2005,10 @@ class AuthSrv(object):
# append additive args from argv to volflags
hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
for name in "mtp on404 on403".split() + hooks:
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
for name in "ext_th mtp on404 on403".split() + hooks:
self._read_volflag(
vol.vpath, vol.flags, name, getattr(self.args, name), True
)
for hn in hooks:
cmds = vol.flags.get(hn)
@@ -2003,6 +2036,16 @@ class AuthSrv(object):
ncmds.append(ocmd)
vol.flags[hn] = ncmds
ext_th = vol.flags["ext_th_d"] = {}
etv = "(?)"
try:
for etv in vol.flags.get("ext_th") or []:
k, v = etv.split("=")
ext_th[k] = v
except:
t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
self.log(t % (vol.vpath, etv), 3)
# d2d drops all database features for a volume
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
if not vol.flags.get(grp, False):
@@ -2134,8 +2177,13 @@ class AuthSrv(object):
for vol in vfs.all_nodes.values():
for k in list(vol.flags.keys()):
if re.match("^-[^-]+$", k):
vol.flags.pop(k[1:], None)
vol.flags.pop(k)
zs = k[1:]
if zs in vol.flags:
vol.flags.pop(k[1:])
else:
t = "WARNING: the config for volume [/%s] tried to remove volflag [%s] by specifying [%s] but that volflag was not already set"
self.log(t % (vol.vpath, zs, k), 3)
if vol.flags.get("dots"):
for name in vol.axs.uread:
@@ -2354,6 +2402,7 @@ class AuthSrv(object):
"sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
}
js_htm = {
"SPINNER": self.args.spinner,
"s_name": self.args.bname,
"have_up2k_idx": "e2d" in vf,
"have_acode": not self.args.no_acode,
@@ -2363,6 +2412,7 @@ class AuthSrv(object):
"have_del": not self.args.no_del,
"have_unpost": int(self.args.unpost),
"have_emp": self.args.emp,
"ext_th": vf.get("ext_th_d") or {},
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
"sba_md": vf.get("md_sba") or "",
"sba_lg": vf.get("lg_sba") or "",
@@ -2383,6 +2433,7 @@ class AuthSrv(object):
"u2j": self.args.u2j,
"u2sz": self.args.u2sz,
"u2ts": vf["u2ts"],
"u2ow": vf["u2ow"],
"frand": bool(vf.get("rand")),
"lifetime": vn.js_ls["lifetime"],
"u2sort": self.args.u2sort,
@@ -2767,7 +2818,9 @@ class AuthSrv(object):
zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
lst = set(zs.split())
askip = set("a v c vc cgen exp_lg exp_md theme".split())
fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
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"
fskip = set(t.split())
# keymap from argv to vflag
amap = vf_bmap()

View File

@@ -5,6 +5,9 @@ from __future__ import print_function, unicode_literals
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"
onedash = set(zs.split())
# verify that all volflags are documented here:
# 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
def vf_bmap() -> dict[str, str]:
"""argv-to-volflag: simple bools"""
@@ -40,6 +43,7 @@ def vf_bmap() -> dict[str, str]:
"gsel",
"hardlink",
"magic",
"no_db_ip",
"no_sb_md",
"no_sb_lg",
"nsort",
@@ -70,6 +74,7 @@ def vf_vmap() -> dict[str, str]:
}
for k in (
"dbd",
"forget_ip",
"hsortn",
"html_head",
"lg_sbf",
@@ -77,6 +82,7 @@ def vf_vmap() -> dict[str, str]:
"lg_sba",
"md_sba",
"nrand",
"u2ow",
"og_desc",
"og_site",
"og_th",
@@ -106,6 +112,7 @@ def vf_cmap() -> dict[str, str]:
for k in (
"exp_lg",
"exp_md",
"ext_th",
"mte",
"mth",
"mtp",
@@ -152,7 +159,8 @@ flagcats = {
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
"nosub": "forces all uploads into the top folder of the vfs",
"magic": "enables filetype detection for nameless uploads",
"gz": "allows server-side gzip of uploads with ?gz (also c,xz)",
"gz": "allows server-side gzip compression of uploads with ?gz",
"xz": "allows server-side lzma compression of uploads with ?xz",
"pk": "forces server-side compression, optional arg: xz,9",
},
"upload rules": {
@@ -163,6 +171,7 @@ flagcats = {
"medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
"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",
"u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
"u2abort=1": "allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk",
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
@@ -179,8 +188,11 @@ flagcats = {
"e2dsa": "scans all folders for new files on startup; also sets -e2d",
"e2t": "enable multimedia indexing; makes it possible to search for tags",
"e2ts": "scan existing files for tags on startup; also sets -e2t",
"e2tsa": "delete all metadata from DB (full rescan); also sets -e2ts",
"e2tsr": "delete all metadata from DB (full rescan); also sets -e2ts",
"d2ts": "disables metadata collection for existing files",
"e2v": "verify integrity on startup by hashing files and comparing to db",
"e2vu": "when e2v fails, update the db (assume on-disk files are good)",
"e2vp": "when e2v fails, panic and quit copyparty",
"d2ds": "disables onboot indexing, overrides -e2ds*",
"d2t": "disables metadata collection, overrides -e2t*",
"d2v": "disables file verification, overrides -e2v*",
@@ -190,6 +202,8 @@ 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",
"forget_ip=43200": "forget uploader-IP after 30 days (GDPR)",
"no_db_ip": "never store uploader-IP in the db; disables unpost",
"fat32": "avoid excessive reindexing on android sdcardfs",
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
"xlink": "cross-volume dupe detection / linking (dangerous)",
@@ -200,6 +214,8 @@ flagcats = {
"srch_excl": "exclude search results with URL matching this regex",
},
'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
"mte=artist,title": "media-tags to index/display",
"mth=fmt,res,ac": "media-tags to hide by default",
"mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
"mtp=ahash,vhash=media-hash.py": "collects two tags at once",
},
@@ -213,6 +229,7 @@ flagcats = {
"crop": "center-cropping (y/n/fy/fn)",
"th3x": "3x resolution (y/n/fy/fn)",
"convt": "conversion timeout in seconds",
"ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
},
"handlers\n(better explained in --help-handlers)": {
"on404=PY": "handle 404s by executing PY file",
@@ -235,8 +252,12 @@ flagcats = {
"grid": "show grid/thumbnails by default",
"gsel": "select files in grid by ctrl-click",
"sort": "default sort order",
"nsort": "natural-sort of leading digits in filenames",
"hsortn": "number of sort-rules to add to media URLs",
"unlist": "dont list files matching REGEX",
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
"tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
"nodirsz": "don't show total folder size",
"robots": "allows indexing by search engines (default)",
"norobots": "kindly asks search engines to leave",
"no_sb_md": "disable js sandbox for markdown files",
@@ -249,12 +270,36 @@ flagcats = {
"lg_sba": "value of iframe allow-prop for *logue-sandbox",
"nohtml": "return html and markdown as text/html",
},
"opengraph (discord embeds)": {
"og": "enable OG (disables hotlinking)",
"og_site": "sitename; defaults to --name, disable with '-'",
"og_desc": "description text for all files; disable with '-'",
"og_th=jf": "thumbnail format; j / jf / jf3 / w / w3 / ...",
"og_title_a": "audio title format; default: {{ artist }} - {{ title }}",
"og_title_v": "video title format; default: {{ title }}",
"og_title_i": "image title format; default: {{ title }}",
"og_title=foo": "fallback title if there's nothing in the db",
"og_s_title": "force default title; do not read from tags",
"og_tpl": "custom html; see --og-tpl in --help",
"og_no_head": "you want to add tags manually with og_tpl",
"og_ua": "if defined: only send OG html if useragent matches this regex",
},
"textfiles": {
"exp": "enable textfile expansion; see --help-exp",
"exp_md": "placeholders to expand in markdown files; see --help",
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
},
"others": {
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
"dk=8": 'generates per-directory accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
"dks": "per-directory accesskeys allow browsing into subdirs",
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
"rss": "allow '?rss' URL suffix (experimental)",
"ups_who=2": "restrict viewing the list of recent uploads",
"zip_who=2": "restrict access to download-as-zip/tar",
"nopipe": "disable race-the-beam (download unfinished uploads)",
"mv_retry": "ms-windows: timeout for renaming busy files",
"rm_retry": "ms-windows: timeout for deleting busy files",
"davauth": "ask webdav clients to login for all folders",
@@ -264,3 +309,10 @@ flagcats = {
flagdescs = {k.split("=")[0]: v for tab in flagcats.values() for k, v in tab.items()}
if True: # so it gets removed in release-builds
for fun in [vf_bmap, vf_cmap, vf_vmap]:
for k in fun().values():
if k not in flagdescs:
raise Exception("undocumented volflag: " + k)

View File

@@ -152,6 +152,8 @@ RE_HSAFE = re.compile(r"[\x00-\x1f<>\"'&]") # search always much faster
RE_HOST = re.compile(r"[^][0-9a-zA-Z.:_-]") # search faster <=17ch
RE_MHOST = re.compile(r"^[][0-9a-zA-Z.:_-]+$") # match faster >=18ch
RE_K = re.compile(r"[^0-9a-zA-Z_-]") # search faster <=17ch
RE_HR = re.compile(r"[<>\"'&]")
RE_MDV = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[Mm][Dd])$")
UPARAM_CC_OK = set("doc move tree".split())
@@ -191,7 +193,7 @@ class HttpCli(object):
self.is_vproxied = False
self.in_hdr_recv = True
self.headers: dict[str, str] = {}
self.mode = " "
self.mode = " " # http verb
self.req = " "
self.http_ver = ""
self.hint = ""
@@ -731,10 +733,10 @@ class HttpCli(object):
return self.handle_unlock() and self.keepalive
elif self.mode == "MKCOL":
return self.handle_mkcol() and self.keepalive
elif self.mode == "MOVE":
return self.handle_move() and self.keepalive
elif self.mode in ("MOVE", "COPY"):
return self.handle_cpmv() and self.keepalive
else:
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode))
except Exception as ex:
if not isinstance(ex, Pebkac):
@@ -1776,6 +1778,12 @@ class HttpCli(object):
if "%" in self.req:
self.log(" `-- %r" % (self.vpath,))
if self.args.no_dav:
raise Pebkac(405, "WebDAV is disabled in server config")
if not self.can_write:
raise Pebkac(401, "authenticate")
try:
return self._mkdir(self.vpath, True)
except Pebkac as ex:
@@ -1785,14 +1793,36 @@ class HttpCli(object):
self.reply(b"", ex.code)
return True
def handle_move(self) -> bool:
def handle_cpmv(self) -> bool:
dst = self.headers["destination"]
dst = re.sub("^https?://[^/]+", "", dst).lstrip()
dst = unquotep(dst)
if not self._mv(self.vpath, dst.lstrip("/")):
return False
return True
# dolphin (kioworker/6.10) "webdav://127.0.0.1:3923/a/b.txt"
dst = re.sub("^[a-zA-Z]+://[^/]+", "", dst).lstrip()
if self.is_vproxied and dst.startswith(self.args.SRS):
dst = dst[len(self.args.RS) :]
if self.do_log:
self.log("%s %s --//> %s @%s" % (self.mode, self.req, dst, self.uname))
if "%" in self.req:
self.log(" `-- %r" % (self.vpath,))
if self.args.no_dav:
raise Pebkac(405, "WebDAV is disabled in server config")
dst = unquotep(dst)
# overwrite=True is default; rfc4918 9.8.4
zs = self.headers.get("overwrite", "").lower()
overwrite = zs not in ["f", "false"]
try:
fun = self._cp if self.mode == "COPY" else self._mv
return fun(self.vpath, dst.lstrip("/"), overwrite)
except Pebkac as ex:
if ex.code == 403:
ex.code = 401
raise
def _applesan(self) -> bool:
if self.args.dav_mac or "Darwin/" not in self.ua:
@@ -4809,9 +4839,12 @@ class HttpCli(object):
# that the client is not a graphical browser
if (
rc == 403
and not self.pw
and not self.ua.startswith("Mozilla/")
and self.uname == "*"
and "sec-fetch-site" not in self.headers
and (
not self.ua.startswith("Mozilla/")
or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
)
):
rc = 401
self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
@@ -5441,6 +5474,8 @@ class HttpCli(object):
def handle_rm(self, req: list[str]) -> bool:
if not req and not self.can_delete:
if self.mode == "DELETE" and self.uname == "*":
raise Pebkac(401, "authenticate") # webdav
raise Pebkac(403, "'delete' not allowed for user " + self.uname)
if self.args.no_del:
@@ -5475,14 +5510,22 @@ class HttpCli(object):
if not dst:
raise Pebkac(400, "need dst vpath")
return self._mv(self.vpath, dst.lstrip("/"))
return self._mv(self.vpath, dst.lstrip("/"), False)
def _mv(self, vsrc: str, vdst: str) -> bool:
def _mv(self, vsrc: str, vdst: str, overwrite: bool) -> bool:
if self.args.no_mv:
raise Pebkac(403, "the rename/move feature is disabled in server config")
self.asrv.vfs.get(vsrc, self.uname, True, False, True)
self.asrv.vfs.get(vdst, self.uname, False, True)
# `handle_cpmv` will catch 403 from these and raise 401
svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False, True)
dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
if overwrite:
dabs = dvn.canonical(drem)
if bos.path.exists(dabs):
self.log("overwriting %s" % (dabs,))
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
wunlink(self.log, dabs, dvn.flags)
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
self.loud_reply(x.get(), status=201)
@@ -5498,14 +5541,21 @@ class HttpCli(object):
if not dst:
raise Pebkac(400, "need dst vpath")
return self._cp(self.vpath, dst.lstrip("/"))
return self._cp(self.vpath, dst.lstrip("/"), False)
def _cp(self, vsrc: str, vdst: str) -> bool:
def _cp(self, vsrc: str, vdst: str, overwrite: bool) -> bool:
if self.args.no_cp:
raise Pebkac(403, "the copy feature is disabled in server config")
self.asrv.vfs.get(vsrc, self.uname, True, False)
self.asrv.vfs.get(vdst, self.uname, False, True)
svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False)
dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
if overwrite:
dabs = dvn.canonical(drem)
if bos.path.exists(dabs):
self.log("overwriting %s" % (dabs,))
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
wunlink(self.log, dabs, dvn.flags)
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
self.loud_reply(x.get(), status=201)
@@ -5921,7 +5971,7 @@ class HttpCli(object):
# [num-backups, most-recent, hist-path]
hist: dict[str, tuple[int, float, str]] = {}
histdir = os.path.join(fsroot, ".hist")
ptn = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[^\.]+)$")
ptn = RE_MDV
try:
for hfn in bos.listdir(histdir):
m = ptn.match(hfn)
@@ -5954,6 +6004,7 @@ class HttpCli(object):
dirs = []
files = []
ptn_hr = RE_HR
for fn in ls_names:
base = ""
href = fn
@@ -6008,11 +6059,13 @@ class HttpCli(object):
zd.second,
)
try:
ext = "---" if is_dir else fn.rsplit(".", 1)[1]
if is_dir:
ext = "---"
elif "." in fn:
ext = ptn_hr.sub("@", fn.rsplit(".", 1)[1])
if len(ext) > 16:
ext = ext[:16]
except:
else:
ext = "%"
if add_fk and not is_dir:

View File

@@ -769,7 +769,7 @@ class SvcHub(object):
vs = os.path.expandvars(os.path.expanduser(vs))
setattr(al, k, vs)
for k in "sus_urls nonsus_urls".split(" "):
for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
vs = getattr(al, k)
if not vs or vs == "no":
setattr(al, k, None)

View File

@@ -557,6 +557,7 @@ class Up2k(object):
else:
# important; not deferred by db_act
timeout = self._check_lifetimes()
timeout = min(self._check_forget_ip(), timeout)
try:
if self.args.shr:
timeout = min(self._check_shares(), timeout)
@@ -617,6 +618,43 @@ class Up2k(object):
for v in vols:
volage[v] = now
def _check_forget_ip(self) -> float:
now = time.time()
timeout = now + 9001
for vp, vol in sorted(self.vfs.all_vols.items()):
maxage = vol.flags["forget_ip"]
if not maxage:
continue
cur = self.cur.get(vol.realpath)
if not cur:
continue
cutoff = now - maxage * 60
for _ in range(2):
q = "select ip, at from up where ip > '' order by +at limit 1"
hits = cur.execute(q).fetchall()
if not hits:
break
remains = hits[0][1] - cutoff
if remains > 0:
timeout = min(timeout, now + remains)
break
q = "update up set ip = '' where ip > '' and at <= %d"
cur.execute(q % (cutoff,))
zi = cur.rowcount
cur.connection.commit()
t = "forget-ip(%d) removed %d IPs from db [/%s]"
self.log(t % (maxage, zi, vol.vpath))
timeout = min(timeout, now + 900)
return timeout
def _check_lifetimes(self) -> float:
now = time.time()
timeout = now + 9001
@@ -1081,7 +1119,7 @@ class Up2k(object):
ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}"
fv = "\033[0;36m{}:\033[90m{}"
zs = "html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
fx = set(zs.split())
fd = vf_bmap()
fd.update(vf_cmap())
@@ -2916,9 +2954,14 @@ class Up2k(object):
self.salt, cj["size"], cj["lmod"], cj["prel"], cj["name"]
)
if vfs.flags.get("up_ts", "") == "fu" or not cj["lmod"]:
zi = cj["lmod"]
bad_mt = zi <= 0 or zi > 0xAAAAAAAA
if bad_mt or vfs.flags.get("up_ts", "") == "fu":
# force upload time rather than last-modified
cj["lmod"] = int(time.time())
if zi and bad_mt:
t = "ignoring impossible last-modified time from client: %s"
self.log(t % (zi,), 6)
alts: list[tuple[int, int, dict[str, Any], "sqlite3.Cursor", str, str]] = []
for ptop, cur in vols:
@@ -3335,7 +3378,17 @@ class Up2k(object):
return fname
fp = djoin(fdir, fname)
if job.get("replace") and bos.path.exists(fp):
ow = job.get("replace") and bos.path.exists(fp)
if ow and "mt" in str(job["replace"]).lower():
mts = bos.stat(fp).st_mtime
mtc = job["lmod"]
if mtc < mts:
t = "will not overwrite; server %d sec newer than client; %d > %d %r"
self.log(t % (mts - mtc, mts, mtc, fp))
ow = False
if ow:
self.log("replacing existing file at %r" % (fp,))
cur = None
ptop = job["ptop"]
@@ -3789,7 +3842,7 @@ class Up2k(object):
db_ip = ""
else:
# plugins may expect this to look like an actual IP
db_ip = "1.1.1.1" if self.args.no_db_ip else ip
db_ip = "1.1.1.1" if "no_db_ip" in vflags else ip
sql = "insert into up values (?,?,?,?,?,?,?)"
v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0))
@@ -4628,12 +4681,12 @@ class Up2k(object):
Optional[str],
Optional[int],
Optional[int],
Optional[str],
str,
Optional[int],
]:
cur = self.cur.get(ptop)
if not cur:
return None, None, None, None, None, None
return None, None, None, None, "", None
rd, fn = vsplit(vrem)
q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1"
@@ -4647,7 +4700,7 @@ class Up2k(object):
if hit:
wark, ftime, fsize, ip, at = hit
return cur, wark, ftime, fsize, ip, at
return cur, None, None, None, None, None
return cur, None, None, None, "", None
def _forget_file(
self,

View File

@@ -1695,15 +1695,24 @@ html.y #tree.nowrap .ntree a+a:hover {
line-height: 0;
}
.dumb_loader_thing {
display: inline-block;
display: block;
margin: 1em .3em 1em 1em;
padding: 0 1.2em 0 0;
font-size: 4em;
min-width: 1em;
min-height: 1em;
opacity: 0;
animation: 1s linear .15s infinite forwards spin, .2s ease .15s 1 forwards fadein;
position: absolute;
position: fixed;
top: .3em;
z-index: 9;
}
#dlt_t {
left: 0;
}
#dlt_f {
right: .5em;
}
#files .cfg {
display: none;
font-size: 2em;

View File

@@ -151,7 +151,8 @@ var Ls = {
"ul_par": "parallel uploads:",
"ut_rand": "randomize filenames",
"ut_u2ts": "copy the last-modified timestamp$Nfrom your filesystem to the server",
"ut_u2ts": "copy the last-modified timestamp$Nfrom your filesystem to the server\">📅",
"ut_ow": "overwrite existing files on the server?$N🛡: never (will generate a new filename instead)$N🕒: overwrite if server-file is older than yours$N♻: always overwrite if the files are different",
"ut_mt": "continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck",
"ut_ask": 'ask for confirmation before upload starts">💭',
"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
@@ -538,6 +539,7 @@ var Ls = {
"u_ewrite": 'you do not have write-access to this folder',
"u_eread": 'you do not have read-access to this folder',
"u_enoi": 'file-search is not enabled in server config',
"u_enoow": "overwrite will not work here; need Delete-permission",
"u_badf": 'These {0} files (of {1} total) were skipped, possibly due to filesystem permissions:\n\n',
"u_blankf": 'These {0} files (of {1} total) are blank / empty; upload them anyways?\n\n',
"u_just1": '\nMaybe it works better if you select just one file',
@@ -751,7 +753,8 @@ var Ls = {
"ul_par": "samtidige handl.:",
"ut_rand": "finn opp nye tilfeldige filnavn",
"ut_u2ts": "gi filen på serveren samme$Ntidsstempel som lokalt hos deg",
"ut_u2ts": "gi filen på serveren samme$Ntidsstempel som lokalt hos deg\">📅",
"ut_ow": "overskrive eksisterende filer på serveren?$N🛡: aldri (finner på et nytt filnavn istedenfor)$N🕒: overskriv hvis serverens fil er eldre$N♻: alltid, gitt at innholdet er forskjellig",
"ut_mt": "fortsett å befare køen mens opplastning foregår$N$Nskru denne av dersom du har en$Ntreg prosessor eller harddisk",
"ut_ask": 'bekreft filutvalg før opplastning starter">💭',
"ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet",
@@ -1138,6 +1141,7 @@ var Ls = {
"u_ewrite": 'du har ikke skrivetilgang i denne mappen',
"u_eread": 'du har ikke lesetilgang i denne mappen',
"u_enoi": 'filsøk er deaktivert i serverkonfigurasjonen',
"u_enoow": "kan ikke overskrive filer her (Delete-rettigheten er nødvendig)",
"u_badf": 'Disse {0} filene (av totalt {1}) kan ikke leses, kanskje pga rettighetsproblemer i filsystemet på datamaskinen din:\n\n',
"u_blankf": 'Disse {0} filene (av totalt {1}) er blanke / uten innhold; ønsker du å laste dem opp uansett?\n\n',
"u_just1": '\nFunker kanskje bedre hvis du bare tar én fil om gangen',
@@ -1351,7 +1355,8 @@ var Ls = {
"ul_par": "并行上传:",
"ut_rand": "随机化文件名",
"ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器",
"ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器\">📅",
"ut_ow": "覆盖服务器上的现有文件?$N🛡: 从不(会生成一个新文件名)$N🕒: 服务器文件较旧则覆盖$N♻: 总是覆盖,如果文件内容不同", //m
"ut_mt": "在上传时继续哈希其他文件$N$N如果你的 CPU 或硬盘是瓶颈,可能需要禁用",
"ut_ask": '上传开始前询问确认">💭',
"ut_pot": "通过简化 UI 来$N提高慢设备上的上传速度",
@@ -1738,6 +1743,7 @@ var Ls = {
"u_ewrite": '你对这个文件夹没有写入权限',
"u_eread": '你对这个文件夹没有读取权限',
"u_enoi": '文件搜索在服务器配置中未启用',
"u_enoow": "无法覆盖此处的文件;需要删除权限", //m
"u_badf": '这些 {0} 个文件(共 {1} 个)被跳过,可能是由于文件系统权限:\n\n',
"u_blankf": '这些 {0} 个文件(共 {1} 个)是空白的;是否仍然上传?\n\n',
"u_just1": '\n也许如果你只选择一个文件会更好',
@@ -1918,8 +1924,8 @@ ebi('op_up2k').innerHTML = (
' <label for="u2rand" tt="' + L.ut_rand + '">🎲</label>\n' +
' </td>\n' +
' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="u2ts" />\n' +
' <label for="u2ts" tt="' + L.ut_u2ts + '">📅</a>\n' +
' <input type="checkbox" id="u2ow" />\n' +
' <label for="u2ow" tt="' + L.ut_ow + '">?</a>\n' +
' </td>\n' +
' <td class="c" data-perm="read" data-dep="idx" rowspan="2">\n' +
' <input type="checkbox" id="fsearch" />\n' +
@@ -2037,6 +2043,7 @@ ebi('op_cfg').innerHTML = (
' <h3>' + L.cl_uopts + '</h3>\n' +
' <div>\n' +
' <a id="ask_up" class="tgl btn" href="#" tt="' + L.ut_ask + '</a>\n' +
' <a id="u2ts" class="tgl btn" href="#" tt="' + L.ut_u2ts + '</a>\n' +
' <a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
' <a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
@@ -2173,6 +2180,11 @@ function goto(dest) {
}
var m = SPINNER.split(','),
SPINNER_CSS = SPINNER.slice(1 + m[0].length);
SPINNER = m[0];
var SBW, SBH; // scrollbar size
(function () {
var el = mknod('div');
@@ -4426,7 +4438,7 @@ function read_dsort(txt) {
}
}
catch (ex) {
toast.warn(10, 'failed to apply default sort order [' + txt + ']:\n' + ex);
toast.warn(10, 'failed to apply default sort order [' + esc('' + txt) + ']:\n' + ex);
dsort = [['href', 1, '']];
}
}
@@ -6294,19 +6306,40 @@ var thegrid = (function () {
var html = [],
svgs = new Set(),
max_svgs = CHROME ? 500 : 5000,
need_ext = !r.thumbs || !!ext_th,
use_ext_th = r.thumbs && ext_th,
files = QSA('#files>tbody>tr>td:nth-child(2) a[id]');
for (var a = 0, aa = files.length; a < aa; a++) {
var ao = files[a],
ohref = esc(ao.getAttribute('href')),
href = ohref.split('?')[0],
ext = '',
name = uricom_dec(vsplit(href)[1]),
ref = ao.getAttribute('id'),
isdir = href.endsWith('/'),
ac = isdir ? ' class="dir"' : '',
ihref = ohref;
if (r.thumbs) {
if (need_ext && href != "#") {
var ar = href.split('.');
if (ar.length > 1)
ar.shift();
ar.reverse();
for (var b = 0; b < Math.min(2, ar.length); b++) {
if (ar[b].length > 7)
break;
ext = ar[b] + '.' + ext;
}
ext = (ext || 'unk.').slice(0, -1);
}
if (use_ext_th && ext_th[ext]) {
ihref = ext_th[ext];
}
else if (r.thumbs) {
ihref = addq(ihref, 'th=' + (have_webp ? 'w' : 'j'));
if (!r.crop)
ihref += 'f';
@@ -6319,22 +6352,6 @@ var thegrid = (function () {
ihref = SR + '/.cpr/ico/folder';
}
else {
var ar = href.split('.');
if (ar.length > 1)
ar = ar.slice(1);
ihref = '';
ar.reverse();
for (var b = 0; b < ar.length; b++) {
if (ar[b].length > 7)
break;
ihref = ar[b] + '.' + ihref;
}
if (!ihref) {
ihref = 'unk.';
}
var ext = ihref.slice(0, -1);
if (!svgs.has(ext)) {
if (svgs.size < max_svgs)
svgs.add(ext);
@@ -7493,7 +7510,7 @@ var treectl = (function () {
xhr.open('GET', addq(dst, 'tree=' + top + (r.dots ? '&dots' : '') + k), true);
xhr.onload = xhr.onerror = r.recvtree;
xhr.send();
enspin('#tree');
enspin('t');
}
r.recvtree = function () {
@@ -7541,7 +7558,7 @@ var treectl = (function () {
}
}
}
despin('#tree');
qsr('#dlt_t');
try {
QS('#treeul>li>a+a').textContent = '[root]';
@@ -7701,8 +7718,8 @@ var treectl = (function () {
r.sb_msg = false;
r.nextdir = xhr.top;
clearTimeout(mpl.t_eplay);
enspin('#tree');
enspin(thegrid.en ? '#gfiles' : '#files');
enspin('t');
enspin('f');
window.removeEventListener('scroll', r.tscroll);
}
@@ -7797,9 +7814,8 @@ var treectl = (function () {
}
r.gentab(this.top, res);
despin('#tree');
despin('#files');
despin('#gfiles');
qsr('#dlt_t');
qsr('#dlt_f');
var lg0 = res.logues ? res.logues[0] || "" : "",
lg1 = res.logues ? res.logues[1] || "" : "",
@@ -8188,20 +8204,15 @@ var treectl = (function () {
})();
function enspin(sel) {
despin(sel);
var d = mknod('div');
function enspin(i) {
i = 'dlt_' + i;
if (ebi(i))
return;
var d = mknod('div', i, SPINNER);
d.className = 'dumb_loader_thing';
d.innerHTML = '🌲';
var tgt = QS(sel);
tgt.insertBefore(d, tgt.childNodes[0]);
}
function despin(sel) {
var o = QSA(sel + '>.dumb_loader_thing');
for (var a = o.length - 1; a >= 0; a--)
o[a].parentNode.removeChild(o[a]);
if (SPINNER_CSS)
d.style.cssText = SPINNER_CSS;
document.body.appendChild(d);
}
@@ -8368,7 +8379,7 @@ function mk_files_header(taglist) {
var tag = taglist[a],
c1 = tag.slice(0, 1).toUpperCase();
tag = c1 + tag.slice(1);
tag = esc(c1 + tag.slice(1));
if (c1 == '.')
tag = '<th name="tags/' + tag + '" sort="int"><span>' + tag.slice(1);
else
@@ -8685,7 +8696,17 @@ var mukey = (function () {
var light, theme, themen;
var settheme = (function () {
var ax = 'abcdefghijklmnopqrstuvwx';
var r = {},
ax = 'abcdefghijklmnopqrstuvwx',
tre = '🌲',
chldr = !SPINNER_CSS && SPINNER == tre;
r.ldr = {
'4':['🌴'],
'5':['🌭', 'padding:0 0 .7em .7em;filter:saturate(3)'],
'6':['📞', 'padding:0;filter:brightness(2) sepia(1) saturate(3) hue-rotate(60deg)'],
'7':['▲', 'font-size:3em'], //cp437
};
theme = sread('cpp_thm') || 'a';
if (!/^[a-x][yz]/.exec(theme))
@@ -8715,13 +8736,19 @@ var settheme = (function () {
ebi('themes').innerHTML = html.join('');
var btns = QSA('#themes a');
for (var a = 0; a < themes; a++)
btns[a].onclick = settheme;
btns[a].onclick = r.go;
if (chldr) {
var x = r.ldr[itheme] || [tre];
SPINNER = x[0];
SPINNER_CSS = x[1];
}
bcfg_set('light', light);
tt.att(ebi('themes'));
}
function settheme(e) {
r.go = function (e) {
var i = e;
try { ev(e); i = e.target.textContent; } catch (ex) { }
light = i % 2 == 1;
@@ -8734,7 +8761,7 @@ var settheme = (function () {
}
freshen();
return settheme;
return r;
})();

View File

@@ -1078,26 +1078,28 @@ action_stack = (function () {
var p1 = from.length,
p2 = to.length;
while (p1-- > 0 && p2-- > 0)
while (p1 --> 0 && p2 --> 0)
if (from[p1] != to[p2])
break;
if (car > ++p1) {
if (car > ++p1)
car = p1;
}
var txt = from.substring(car, p1)
return {
car: car,
cdr: ++p2,
cdr: p2 + (car && 1),
txt: txt,
cpos: cpos
};
}
var undiff = function (from, change) {
var t1 = from.substring(0, change.car),
t2 = from.substring(change.cdr);
return {
txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr),
txt: t1 + change.txt + t2,
cpos: change.cpos
};
}

View File

@@ -885,6 +885,25 @@ function up2k_init(subtle) {
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
uc.ow = parseInt(sread('u2ow', ['0', '1', '2']) || u2ow);
uc.owt = ['🛡️', '🕒', '♻️'];
function set_ow() {
QS('label[for="u2ow"]').innerHTML = uc.owt[uc.ow];
ebi('u2ow').checked = true; //cosmetic
}
ebi('u2ow').onclick = function (e) {
ev(e);
if (++uc.ow > 2)
uc.ow = 0;
swrite('u2ow', uc.ow);
set_ow();
if (uc.ow && !has(perms, 'delete'))
toast.warn(10, L.u_enoow, 'noow');
else if (toast.tag == 'noow')
toast.hide();
};
set_ow();
var st = {
"files": [],
"nfile": {
@@ -1300,7 +1319,7 @@ function up2k_init(subtle) {
if (bad_files.length) {
var msg = L.u_badf.format(bad_files.length, ntot);
for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
msg += '-- ' + bad_files[a][1] + '\n';
msg += '-- ' + esc(bad_files[a][1]) + '\n';
msg += L.u_just1;
return modal.alert(msg, function () {
@@ -1312,7 +1331,7 @@ function up2k_init(subtle) {
if (nil_files.length) {
var msg = L.u_blankf.format(nil_files.length, ntot);
for (var a = 0, aa = Math.min(20, nil_files.length); a < aa; a++)
msg += '-- ' + nil_files[a][1] + '\n';
msg += '-- ' + esc(nil_files[a][1]) + '\n';
msg += L.u_just1;
return modal.confirm(msg, function () {
@@ -1390,7 +1409,7 @@ function up2k_init(subtle) {
name = good_files[a][1],
fdir = evpath,
now = Date.now(),
lmod = uc.u2ts ? (fobj.lastModified || now) : 0,
lmod = (uc.u2ts && fobj.lastModified) || 0,
ofs = name.lastIndexOf('/') + 1;
if (ofs) {
@@ -2054,8 +2073,8 @@ function up2k_init(subtle) {
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
reader.onerror = function () {
var err = reader.error + '';
var handled = false;
var err = esc('' + reader.error),
handled = false;
if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
@@ -2279,7 +2298,7 @@ function up2k_init(subtle) {
xhr.onerror = xhr.ontimeout = function () {
console.log('head onerror, retrying', t.name, t);
if (!toast.visible)
toast.warn(9.98, L.u_enethd + "\n\nfile: " + t.name, t);
toast.warn(9.98, L.u_enethd + "\n\nfile: " + esc(t.name), t);
apop(st.busy.head, t);
st.todo.head.unshift(t);
@@ -2354,7 +2373,7 @@ function up2k_init(subtle) {
return console.log('zombie handshake onerror', t.name, t);
if (!toast.visible)
toast.warn(9.98, L.u_eneths + "\n\nfile: " + t.name, t);
toast.warn(9.98, L.u_eneths + "\n\nfile: " + esc(t.name), t);
console.log('handshake onerror, retrying', t.name, t);
apop(st.busy.handshake, t);
@@ -2459,7 +2478,7 @@ function up2k_init(subtle) {
var idx = t.hash.indexOf(missing[a]);
if (idx < 0)
return modal.alert('wtf negative index for hash "{0}" in task:\n{1}'.format(
missing[a], JSON.stringify(t)));
missing[a], esc(JSON.stringify(t))));
t.postlist.push(idx);
cbd[idx] = 0;
@@ -2613,7 +2632,7 @@ function up2k_init(subtle) {
return toast.err(0, L.u_ehsdf + "\n\n" + rsp.replace(/.*; /, ''));
err = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
xhrchk(xhr, err + "\n\nfile: " + t.name + "\n\nerror ", "404, target folder not found", "warn", t);
xhrchk(xhr, err + "\n\nfile: " + esc(t.name) + "\n\nerror ", "404, target folder not found", "warn", t);
}
}
xhr.onload = function (e) {
@@ -2634,6 +2653,13 @@ function up2k_init(subtle) {
else if (t.umod)
req.umod = true;
if (!t.srch) {
if (uc.ow == 1)
req.replace = 'mt';
if (uc.ow == 2)
req.replace = true;
}
xhr.open('POST', t.purl, true);
xhr.responseType = 'text';
xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
@@ -2763,7 +2789,7 @@ function up2k_init(subtle) {
toast.inf(10, L.u_cbusy);
}
else {
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)", "warn", t);
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), "404, target folder not found (???)", "warn", t);
chill(t);
}
orz2(xhr);
@@ -2807,7 +2833,7 @@ function up2k_init(subtle) {
xhr.bsent = 0;
if (!toast.visible)
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), t.name), t);
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), t);
t.nojoin = t.nojoin || t.postlist.length; // maybe rproxy postsize limit
console.log('chunkpit onerror,', t.name, t);

View File

@@ -64,7 +64,7 @@ onmessage = (d) => {
};
reader.onerror = function () {
busy = false;
var err = reader.error + '';
var err = esc('' + reader.error);
if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions

View File

@@ -1,3 +1,167 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0225-0017 `v1.16.15` fix low-severity vuln
<img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
* 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/)
## ⚠️ this fixes a minor vulnerability; CVE-score `3.6`/`10`
[GHSA-m2jw-cj8v-937r](https://github.com/9001/copyparty/security/advisories/GHSA-m2jw-cj8v-937r) aka [CVE-2025-27145](https://www.cve.org/CVERecord?id=CVE-2025-27145) could let an attacker run arbitrary javascript by tricking an authenticated user into uploading files with malicious filenames
* ...but it required some clever social engineering, and is **not likely** to be a cause for concern... ah, better safe than sorry
there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` in case of future important updates, such as [vulnerabilities](https://github.com/9001/copyparty/security) (most recently 2025-02-25)
## recent important news
* [v1.15.0 (2024-09-08)](https://github.com/9001/copyparty/releases/tag/v1.15.0) changed upload deduplication to be default-disabled
* [v1.14.3 (2024-08-30)](https://github.com/9001/copyparty/releases/tag/v1.14.3) fixed a bug that was introduced in v1.13.8 (2024-08-13); this bug could lead to **data loss** -- see the v1.14.3 release-notes for details
## 🧪 new features
* nothing this time
## 🩹 bugfixes
* fix [GHSA-m2jw-cj8v-937r](https://github.com/9001/copyparty/security/advisories/GHSA-m2jw-cj8v-937r) / [CVE-2025-27145](https://www.cve.org/CVERecord?id=CVE-2025-27145) in 438ea6cc
* when trying to upload an empty files by dragging it into the browser, the filename would be rendered as HTML, allowing javascript injection if the filename was malicious
* issue discovered and reported by @JayPatel48 (thx!)
* related issues in errorhandling of uploads 499ae1c7 36866f1d
* these all had the same consequences as the GHSA above, but a network outage was necessary to trigger them
* which would probably have the lucky side-effect of blocking the javascript download, nice
* paranoid fixing of probably-not-even-issues 3adbb2ff
* fix some markdown / texteditor bugs 407531bc
* only indicate file-versions for markdown files in listings, since it's tricky to edit non-textfiles otherwise
* CTRL-C followed by CTRL-V and CTRL-Z in a single-line file would make a character fall off
* ensure safety of extensions
## 🔧 other changes
* readme:
* mention support for running the server on risc-v 6d102fc8
* mention that the [sony psp](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) can browse and upload 598a29a7
----
# 💾 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.16.14/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)) |
| [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) | ⚠️ acceptable | similar to the regular sfx, [mostly worse](https://github.com/9001/copyparty#zipapp) |
| [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.16.5/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.16.14/u2c.exe), all of the options above are mostly equivalent
* the zip and tar.gz files below are just source code
* python packages are available at [PyPI](https://pypi.org/project/copyparty/#files)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0219-2309 `v1.16.14` overwrite by upload
## 🧪 new features
* #139 overwrite existing files by uploading over them e9f78ea7
* default-disabled; a new togglebutton in the upload-UI configures it
* can optionally compare last-modified-time and only overwrite older files
* [GDPR compliance](https://github.com/9001/copyparty#GDPR-compliance) (maybe/probably) 4be0d426
## 🩹 bugfixes
* some cosmetic volflag stuff, all harmless b190e676
* disabling a volflag `foo` with `-foo` shows a warning that `-foo` was not a recognized volflag, but it still does the right thing
* some volflags give the *"unrecognized volflag, will ignore"* warning, but not to worry, they still work just fine:
* `xz` to allow serverside xz-compression of uploaded files
* the option to customize the loader-spinner would glitch out during the initial page load 7d7d5d6c
## 🔧 other changes
* [randpic.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/handlers/randpic.py), new 404-handler example, returns a random pic from a folder 60d5f271
* readme: [howto permanent cloudflare tunnel](https://github.com/9001/copyparty#permanent-cloudflare-tunnel) for easy hosting from home 2beb2acc
* [synology-dsm](https://github.com/9001/copyparty/blob/hovudstraum/docs/synology-dsm.md): mention how to update the docker image 56ce5919
* spinner improvements 6858cb06
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0213-2057 `v1.16.13` configure with confidence
## 🧪 new features
* make the config-parser more helpful regarding volflags a255db70
* if an unrecognized volflag is specified, print a warning instead of silently ignoring it
* understand volflag-names with Uppercase and/or kebab-case (dashes), and not just snake_case (underscores)
* improve `--help-flags` to mention and explain all available flags
* #136 WebDAV: support COPY 62ee7f69
* also support overwrite of existing target files (default-enabled according to the spec)
* the user must have the delete-permission to actually replace files
* option to specify custom icons for certain file extensions 7e4702cf
* see `--ext-th` mentioned briefly in the [thumbnails section](https://github.com/9001/copyparty/#thumbnails)
* option to replace the loading-spinner animation 685f0869
* including how to [make it exceptionally normal-looking](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner)
## 🩹 bugfixes
* #136 WebDAV fixes 62ee7f69
* COPY/MOVE/MKCOL: challenge clients to provide the password as necessary
* most clients only need this in PROPFIND, but KDE-Dolphin is more picky
* MOVE: support `webdav://` Destination prefix as used by Dolphin, probably others
* #136 WebDAV: improve support for KDE-Dolphin as client 9d769027
* it masquerades as a graphical browser yet still expects 401, so special-case it with a useragent scan
## 🔧 other changes
* Docker-only: quick hacky fix for the [musl CVE](https://www.openwall.com/lists/musl/2025/02/13/1) until the official fix is out 4d6626b0
* the docker images will be rebuilt when `musl-1.2.5-r9.apk` is released, in 6~24h or so
* until then, there is no support for reading korean XML files when running in docker
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0209-2331 `v1.16.12` RTT
## 🧪 new features
* show rtt (network latency to server, including request processing time) in the top status text d27f1104
* and log the client-reported RTT to serverlog 20ddeb6e
* remember file selection when changing folders c7db08ed
* good for when you accidentally navigate elsewhere
* option to restrict download-as-zip/tar to admins-only c87af9e8
* #135 add [bubbleparty](https://github.com/9001/copyparty/blob/hovudstraum/bin/README.md#bubblepartysh), thx @coderofsalvation! 3582a100
* runs copyparty in a [sandbox](https://github.com/containers/bubblewrap), making it harder to gain unintended access through bugs in python or copyparty
* better alternative to [prisonparty](https://github.com/9001/copyparty/tree/hovudstraum/bin#prisonpartysh), more similar to [the sandboxing in the nixos package](https://github.com/9001/copyparty/blob/7dda77dcb/contrib/nixos/modules/copyparty.nix#L232-L272)
* new plugin: [quickmove](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/quickmove.js) 46f9e9ef
* adds hotkey `W` to quickly move selected files into a subfolder
* #133 new plugin: [graft-thumbs.js](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/graft-thumbs.js) 6c202eff
* in folders with foobar.mp3 and foobar.png, can copy the thumbnail from the png to the jpg (and then hide the png)
* handlers: add [http-redirect example](https://github.com/9001/copyparty/blob/hovudstraum/bin/handlers/redirect.py) 22cbd2db
* add [ping.html](https://github.com/9001/copyparty/blob/hovudstraum/srv/ping.html) 7de9d15a 910797cc
## 🩹 bugfixes
* improve iPad detection so they get opus instead of mp3 12dcea4f
## 🔧 other changes
* safeguard against accidental config loss cd71b505
* while no copyparty servers have ended up in this unfortunate situation yet (afaik), be proactive and borrow some experience from other docker-based services
* readme: improve config examples 32e90859
* improve serverlog entries regarding 403s b020fd4a
* #132 mention fuse permissions in readme d9d2a092
* traefik-example: fix disconnect during big uploads 6a9ffe7e
* try to show an appropriate warning for media that the browser doesn't support playing 4ef35263
* was an attempt at detecting iphones failing to play high-color-precision webm files, but safari doesn't seem to realize itself that playback has failed, ah well
* copyparty.exe: update to python 3.12.9
* update deps: dompurify 3.2.4
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0127-0140 `v1.16.11` fix no-acode

View File

@@ -48,6 +48,20 @@ and if you want to have a monospace font in the fancy markdown editor, do this:
NB: `<textarea id="mt">` and `<div id="mtr">` in the regular markdown editor must have the same font; none of the suggestions above will cause any issues but keep it in mind if you're getting creative
# boring loader spinner
replace the 🌲 with a spinning circle using commandline args:
`--spinner ',padding:0;border-radius:9em;border:.2em solid #444;border-top:.2em solid #fc0'`
or config file example:
```yaml
[global]
spinner: ,padding:0;border-radius:9em;border:.2em solid #444;border-top:.2em solid #fc0
```
# `<head>`
to add stuff to the html `<head>`, for example a css `<link>` or `<meta>` tags, use either the global-option `--html-head` or the volflag `html_head`

View File

@@ -115,6 +115,16 @@ note that if you only want to share some folders inside your data volume, and no
## updating
to update to a new copyparty version: `Container Manager` » `Images` » `Update available` » `Update`
* DSM checks for updates every 12h; you can force a check with `sudo /var/packages/ContainerManager/target/tool/image_upgradable_checker`
* there is no auto-update feature, and beware that watchtower does not support DSM
## regarding ram usage
the ram usage indicator in both `Docker` and `Container Manager` is misleading because it also counts the kernel disk cache which makes the number insanely high -- the synology resource monitor shows the correct values, usually less than 100 MiB

View File

@@ -131,6 +131,7 @@ symbol legend,
| runs on Linux | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
| runs on Macos | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | |
| runs on FreeBSD | █ | | | • | █ | █ | █ | • | █ | █ | | █ | |
| runs on Risc-V | █ | | | █ | █ | █ | | • | | █ | | | |
| portable binary | █ | █ | █ | | | █ | █ | | | █ | | █ | █ |
| zero setup, just go | █ | █ | █ | | | | █ | | | █ | | | █ |
| android app | | | | █ | █ | | | | | | | | |

View File

@@ -144,16 +144,16 @@ class Cfg(Namespace):
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
ka.update(**{k: 9 for k in ex.split()})
ex = "db_act k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
ex = "db_act forget_ip k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow"
ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src R RS SR"
ka.update(**{k: "" for k in ex.split()})
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
ex = "ban_403 ban_404 ban_422 ban_pw ban_url spinner"
ka.update(**{k: "no" for k in ex.split()})
ex = "grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
ex = "ext_th grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
ka.update(**{k: [] for k in ex.split()})
ex = "exp_lg exp_md"