docs/devnotes.md
I flag runs the hook inside copypartylist of dreams which will probably never happen
up2k.py ended up doing all the file indexing / db managementhttpcli.py should be separated into modules in generalquick outline of the up2k protocol, see uploading for the web-client
wark, an identifier for this upload
sha512( salt + filesize + chunk_hashes )up2k has saved a few uploads from becoming corrupted in-transfer already;
regarding the frequent server log message during uploads;
6.0M 106M/s 2.77G 102.9M/s n948 thank 4/0/3/1 10042/7198 00:01:09
6 MiB, uploaded at 106 MiB/s2.77 GiB transferred, 102.9 MiB/s average, 948 chunks handled4 uploads OK, 0 failed, 3 busy, 1 queued, 10042 MiB total size, 7198 MiB and 00:01:09 leftI didn't know about tus when I made this, but:
a single sha512 would be better, right?
this was due to crypto.subtle not yet providing a streaming api (or the option to seed the sha512 hasher with a starting hash)
as a result, the hashes are much less useful than they could have been (search the server by sha512, provide the sha512 in the response http headers, ...)
however it allows for hashing multiple chunks in parallel, greatly increasing upload speed from fast storage (NVMe, raid-0 and such)
hashwasm would solve the streaming issue but reduces hashing speed for sha512 (xxh128 does 6 GiB/s), and it would make old browsers and iphones unsupported
specific chunksizes are enforced depending on total filesize
each pair of filesize/chunksize is the largest filesize which will use its listed chunksize; a 512 MiB file will use chunksize 2 MiB, but if the file is one byte larger than 512 MiB then it becomes 3 MiB
for the purpose of performance (or dodging arbitrary proxy limitations), it is possible to upload combined and/or partial chunks using stitching and/or subchunks respectively
| filesize | filesize | chunksize | chunksz |
|---|---|---|---|
| 268 435 456 | 256 MiB | 1 048 576 | 1.0 MiB |
| 402 653 184 | 384 MiB | 1 572 864 | 1.5 MiB |
| 536 870 912 | 512 MiB | 2 097 152 | 2.0 MiB |
| 805 306 368 | 768 MiB | 3 145 728 | 3.0 MiB |
| 1 073 741 824 | 1.0 GiB | 4 194 304 | 4.0 MiB |
| 1 610 612 736 | 1.5 GiB | 6 291 456 | 6.0 MiB |
| 2 147 483 648 | 2.0 GiB | 8 388 608 | 8.0 MiB |
| 3 221 225 472 | 3.0 GiB | 12 582 912 | 12 MiB |
| 4 294 967 296 | 4.0 GiB | 16 777 216 | 16 MiB |
| 6 442 450 944 | 6.0 GiB | 25 165 824 | 24 MiB |
| 137 438 953 472 | 128 GiB | 33 554 432 | 32 MiB |
| 206 158 430 208 | 192 GiB | 50 331 648 | 48 MiB |
| 274 877 906 944 | 256 GiB | 67 108 864 | 64 MiB |
| 412 316 860 416 | 384 GiB | 100 663 296 | 96 MiB |
| 549 755 813 888 | 512 GiB | 134 217 728 | 128 MiB |
| 824 633 720 832 | 768 GiB | 201 326 592 | 192 MiB |
| 1 099 511 627 776 | 1.0 TiB | 268 435 456 | 256 MiB |
| 1 649 267 441 664 | 1.5 TiB | 402 653 184 | 384 MiB |
| 2 199 023 255 552 | 2.0 TiB | 536 870 912 | 512 MiB |
| 3 298 534 883 328 | 3.0 TiB | 805 306 368 | 768 MiB |
| 4 398 046 511 104 | 4.0 TiB | 1 073 741 824 | 1.0 GiB |
| 6 597 069 766 656 | 6.0 TiB | 1 610 612 736 | 1.5 GiB |
| 8 796 093 022 208 | 8.0 TiB | 2 147 483 648 | 2.0 GiB |
| 13 194 139 533 312 | 12.0 TiB | 3 221 225 472 | 3.0 GiB |
| 17 592 186 044 416 | 16.0 TiB | 4 294 967 296 | 4.0 GiB |
| 26 388 279 066 624 | 24.0 TiB | 6 442 450 944 | 6.0 GiB |
| 35 184 372 088 832 | 32.0 TiB | 8 589 934 592 | 8.0 GiB |
regarding the curious decisions
there is a static salt for all passwords;
params = URL parameters; ?foo=bar&qux=...body = POST payloadjPOST = json postmPOST = multipart postuPOST = url-encoded postFILE = conventional HTTP file upload entry (rfc1867 et al, filename in Content-Disposition)clients can authenticate in the following ways; the first of these which is not blank will be used:
&pw=foo -- can be disabled with --pw-urlp=A (or renamed, if provided value is lowercase)PW: foo -- can be disabled with --pw-hdr=A (or renamed, if provided value is lowercase)--no-bauthCookie: cppwd=foo on plaintext http, or header Cookie: cppws=foo on https| method | params | result |
|---|---|---|
| GET | ?dl | download file (don't show in-browser) |
| GET | ?ls | list files/folders at URL as JSON |
| GET | ?ls&dots | list files/folders at URL as JSON, including dotfiles |
| GET | ?ls=t | list files/folders at URL as plaintext |
| GET | ?ls=v | list files/folders at URL, terminal-formatted |
| GET | ?opds | list files/folders at URL as opds feed, for e-readers |
| GET | ?lt | in listings, use symlink timestamps rather than targets |
| GET | ?b | list files/folders at URL as simplified HTML |
| GET | ?tree=. | list one level of subdirectories inside URL |
| GET | ?tree | list one level of subdirectories for each level until URL |
| GET | ?tar | download everything below URL as a gnu-tar file |
| GET | ?tar=gz:9 | ...as a gzip-level-9 gnu-tar file |
| GET | ?tar=xz:9 | ...as an xz-level-9 gnu-tar file |
| GET | ?tar=pax | ...as a pax-tar file |
| GET | ?tar=pax,xz | ...as an xz-level-1 pax-tar file |
| GET | ?zip | ...as a zip file |
| GET | ?zip=dos | ...as a WinXP-compatible zip file |
| GET | ?zip=crc | ...as an MSDOS-compatible zip file |
| GET | ?tar&w | pregenerate webp thumbnails |
| GET | ?tar&j | pregenerate jpg thumbnails |
| GET | ?tar&p | pregenerate audio waveforms |
| GET | ?shares | list your shared files/folders |
| GET | ?dls | show active downloads (do this as admin) |
| GET | ?ups | show recent uploads from your IP |
| GET | ?ups&filter=f | ...where URL contains f |
| GET | ?ru | show all recent uploads |
| GET | ?ru&filter=f | ...where URL contains f |
| GET | ?ru&j | ...as json |
| GET | ?mime=foo | specify return mimetype foo |
| GET | ?v | render markdown file at URL |
| GET | ?v | open image/video/audio in mediaplayer |
| GET | ?txt | get file at URL as plaintext |
| GET | ?txt=iso-8859-1 | ...with specific charset |
| GET | ?tail | continuously stream a growing file |
| GET | ?tail=1024 | ...starting from byte 1024 |
| GET | ?tail=-128 | ...starting 128 bytes from the end |
| GET | ?th | get image/video at URL as thumbnail |
| GET | ?th=opus | convert audio file to 128kbps opus |
| GET | ?th=caf | ...in the iOS-proprietary container |
| GET | ?zls | get listing of filepaths in zip file at URL |
| GET | ?zget=path | get specific file from inside a zip file at URL |
| method | body | result |
|---|---|---|
| jPOST | {"q":"foo"} | do a server-wide search; see the [๐] search tab raw field for syntax |
| method | params | body | result |
|---|---|---|---|
| jPOST | ?tar | ["foo","bar"] | download folders foo and bar inside URL as a tar file |
| method | params | result |
|---|---|---|
| POST | ?copy=/foo/bar | copy the file/folder at URL to /foo/bar |
| POST | ?move=/foo/bar | move/rename the file/folder at URL to /foo/bar |
| method | params | body | result |
|---|---|---|---|
| PUT | (binary data) | upload into file at URL | |
| PUT | ?j | (binary data) | ...and reply with json |
| PUT | ?ck | (binary data) | upload without checksum gen (faster) |
| PUT | ?ck=md5 | (binary data) | return md5 instead of sha512 |
| PUT | ?gz | (binary data) | compress with gzip and write into file at URL |
| PUT | ?xz | (binary data) | compress with xz and write into file at URL |
| PUT | ?apnd | (binary data) | append to existing file |
| mPOST | f=FILE | upload FILE into the folder at URL | |
| mPOST | ?j | f=FILE | ...and reply with json |
| mPOST | ?ck | f=FILE | ...and disable checksum gen (faster) |
| mPOST | ?ck=md5 | f=FILE | ...and return md5 instead of sha512 |
| mPOST | ?replace | f=FILE | ...and overwrite existing files |
| mPOST | ?apnd | f=FILE | ...and append to existing files |
| mPOST | ?media | f=FILE | ...and return medialink (not hotlink) |
| mPOST | act=mkdir, name=foo | create directory foo at URL | |
| POST | ?delete | delete URL recursively | |
| POST | ?eshare=rm | stop sharing a file/folder | |
| POST | ?eshare=3 | set expiration to 3 minutes | |
| jPOST | ?share | (complicated) | create temp URL for file/folder |
| jPOST | ?delete | ["/foo","/bar"] | delete /foo and /bar recursively |
| uPOST | msg=foo | send message foo into server log | |
| mPOST | act=tput, body=TEXT | overwrite markdown document at URL |
upload modifiers:
| http-header | url-param | effect |
|---|---|---|
Accept: url | want=url | return just the file URL |
Accept: json | want=json | return upload info as json; same as ?j |
Rand: 4 | rand=4 | generate random filename with 4 characters |
Life: 30 | life=30 | delete file after 30 seconds |
Replace: 1 | replace | overwrite file if exists |
CK: no | ck | disable serverside checksum (maybe faster) |
CK: md5 | ck=md5 | return md5 checksum instead of sha512 |
CK: sha1 | ck=sha1 | return sha1 checksum |
CK: sha256 | ck=sha256 | return sha256 checksum |
CK: b2 | ck=b2 | return blake2b checksum |
CK: b2s | ck=b2s | return blake2s checksum |
life only has an effect if the volume has a lifetime, and the volume lifetime must be greater than the file'sreplace upload-modifier:
replace: 1 works for both PUT and multipart-postreplace only works for multipart-postmsg can be reconfigured with --urlform| method | params | result |
|---|---|---|
| GET | ?reload=cfg | reload config files and rescan volumes |
| GET | ?scan | initiate a rescan of the volume which provides URL |
| GET | ?scan=/a,/b | initiate a rescan of volumes /a and /b |
| GET | ?stack | show a stacktrace of all threads |
| method | params | result |
|---|---|---|
| GET | ?pw=x | logout |
| GET | ?grid | ui: show grid-view |
| GET | ?imgs | ui: show grid-view with thumbnails |
| GET | ?grid=0 | ui: show list-view |
| GET | ?imgs=0 | ui: show list-view |
| GET | ?thumb | ui, grid-mode: show thumbnails |
| GET | ?thumb=0 | ui, grid-mode: show icons |
| POST | ?smsg=foo | send-msg-to-serverlog / run xm hook |
on writing your own hooks
hooks can cause intentional side-effects, such as redirecting an upload into another location, or creating+indexing additional files, or deleting existing files, by returning json on stdout
reloc can redirect uploads before/after uploading has finished, based on filename, extension, file contents, uploader ip/name etc.
idx informs copyparty about a new file to index as a consequence of this upload
del tells copyparty to delete an unrelated file by vpath
for these to take effect, the hook must be defined with the c1 flag; see example reloc-by-ext
a subset of effect types are available for a subset of hook types,
idx and del for all http protocols (up2k / basic-uploader / webdav), but not ftp/tftp/smbc is given, see examples reject-extension and reject-mimetypexbu supports reloc for all http protocols (up2k / basic-uploader / webdav), but not ftp/tftp/smbxau supports reloc for basic-uploader / webdav only, not up2k or ftp/tftp/smb
to trigger indexing of files /foo/1.txt and /foo/bar/2.txt, a hook can print(json.dumps({"idx":{"vp":["/foo/1.txt","/foo/bar/2.txt"]}})) (and replace "idx" with "del" to delete instead)
/ are absolute URLs, but you can also do ../3.txt relative to the destination folder of each uploaded filethe I flag runs the hook inside copyparty, which can be very useful and dangerous:
reduce the size of an sfx by removing features
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
393k size of original sfx.py as of v1.1.3310k after ./scripts/make-sfx.sh re no-cm269k after ./scripts/make-sfx.sh re no-cm no-hlthe features you can opt to drop are
cm/easymde, the "fancy" markdown editor, saves ~89khl, prism, the syntax highlighter, saves ~41kfnt, source-code-pro, the monospace font, saves ~9kfor the repack to work, first run one of the sfx'es once to unpack it
note: you can also just download and run /scripts/copyparty-repack.sh -- this will grab the latest copyparty release from github and do a few repacks; works on linux/macos (and windows with msys2 or WSL)
some third-party code has been vendored into the git repo; some for convenience, some because they have been lightly hacked to fit copyparty's usecase better:
inside the folder /copyparty/stolen is python-libraries which runs on the serverside:
surrogateescape.py (BSD2) can be removed; only needed for python2 supportqrcodegen.py (MIT) can be removed and replaced with a systemwide install of the original qrcodegen.py;
ifaddr (BSD2) can be removed and replaced with a systemwide install of the original ifaddr;
dnslib (MIT) may be deleted and replaced with a systemwide install of the original dnslib, HOWEVER:
inside the folder /copyparty/web/deps (only in distributed archives/builds) is fuse.py, to make it downloadable from the connect-page on the web-ui
inside the folder /copyparty/web (only in distributed archives/builds) is a collection of javascript libraries (produced by deps-docker) which are used clientside by the web-UI:
explained in the main readme, but a quick recap:
argon2-cffi paramiko pyftpdlib pyopenssl pillow rawpy pyzmq python-magic
psutil (not very useful on Linux)impacket because the feature it enables is a security nightmaremutagen because ffmpeg produces better results (albeit slower)pyvips because converting to jxl is extremely RAM-heavypillow-heif due to legal reasonsffmpeg ffprobe cfssl cfssljson cfssl-certinfo
you need python 3.9 or newer due to type hints
setting up a venv with the below packages is only necessary if you want it for vscode or similar
python3 -m venv .venv
. .venv/bin/activate
pip install jinja2 strip_hints # MANDATORY
pip install argon2-cffi # password hashing
pip install pyzmq # send 0mq from hooks
pip install mutagen # audio metadata
pip install paramiko # sftp server
pip install pyftpdlib # ftp server
pip install partftpy # tftp server
pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows
pip install Pillow pillow-heif # thumbnails
pip install pyvips # faster thumbnails
pip install psutil # better cleanup of stuck metadata parsers on windows
pip install black==21.12b0 click==8.0.2 bandit pylint flake8 isort mypy # vscode tooling
on archlinux you can do this:
sudo pacman -Sy --needed python-{pip,isort,jinja,argon2-cffi,pyzmq,mutagen,paramiko,pyftpdlib,pillow}python3 -m pip install --user --break-system-packages -U strip_hints black==21.12b0 click==8.0.2sudo pacman -Sy --needed qemu-user-static{,-binfmt} podman{,-docker} jqand if you want to run the python 2.7 tests:
git clone https://github.com/pyenv/pyenv .pyenv ; cd .pyenv/bin ; env PYTHON_CONFIGURE_OPTS='--enable-optimizations' PYTHON_CFLAGS='-march=native -mtune=native -std=c17' ./pyenv install 2.7.18 -v ; ln -s $HOME/.pyenv/versions/2.7.18/bin/python2 $HOME/bin/if you just want to modify the copyparty source code (py/html/css/js) then this is the easiest approach
build the sfx using any of the following examples:
./scripts/make-sfx.sh # regular edition
./scripts/make-sfx.sh fast # build faster (worse js/css compression)
./scripts/make-sfx.sh gz no-cm # gzip-compressed + no fancy markdown editor
uses the included prebuilt webdeps
if you downloaded a release source tarball from github (for example copyparty-1.6.15.tar.gz so not the autogenerated one) you can build it like so,
python3 -m pip install --user -U build setuptools wheel jinja2 strip_hints
bash scripts/run-tests.sh python3 # optional
python3 -m build
if you are unable to use build, you can use the old setuptools approach instead,
python3 setup.py install --user setuptools wheel jinja2
python3 setup.py build
python3 setup.py bdist_wheel
# you now have a wheel which you can install. or extract and repackage:
python3 setup.py install --skip-build --prefix=/usr --root=$HOME/pe/copyparty
how the sausage is made:
to get started, first cd into the scripts folder
the first step is the webdeps; they end up in ../copyparty/web/deps/ for example ../copyparty/web/deps/marked.js.gz -- if you need to build the webdeps, run make -C deps-docker
podman-docker compat-layer to pretend it's docker, although it should be possible to use rootful/rootless docker toosudo make -C deps-docker is fine too./make-sfx.sh fast dl-wdnext, build copyparty-sfx.py by running ./make-sfx.sh gz fast
fast makes it compress bettergz too compresses even better, but startup gets slowerif you want to build the .pyz standalone "binary", now run ./make-pyz.sh
if you want to build the tar.gz for use in a linux-distro package, now run ./make-tgz-release.sh theVersionNumber
if you want to build a pypi package, now run ./make-pypi-release.sh d
if you want to build a docker-image, you have two options:
(cd docker; ./make.sh hclean; ./make.sh hclean pull img)sudo make -C dockerdocker/make.sh or docker/Makefile for inspirationif you want to build the windows exe, first grab some snacks and a beer, you'll need it
the complete list of buildtime dependencies to do a build from scratch is as follows:
sudo apt install make zip bzip2
also builds the sfx so skip the sfx section above
WARNING: rls.sh has not yet been updated with the docker-images and arch/nix packaging
does everything completely from scratch, straight from your local repo
in the scripts folder:
make -C deps-docker to build all dependencies./rls.sh 1.2.3 which uploads to pypi + creates github release + sfxmostly fine on android, but still haven't find a way to massage iphones into behaving well
can be reproduced with --no-sendfile --s-wr-sz 8192 --s-wr-slp 0.3 --rsp-slp 6 and then play a collection of small audio files with the screen off, ffmpeg -i track01.cdda.flac -c:a libopus -b:a 128k -segment_time 12 -f segment smol-%02d.opus
HttpCli / httpcli.py
self.conn.hsrv with a local hsrv variable+mt.k)os.copy_file_range for up2k cloning