├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── something-else.md ├── branch-rename.md └── pull_request_template.md ├── .gitignore ├── .vscode ├── launch.json ├── launch.py ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── bin ├── README.md ├── bubbleparty.sh ├── dbtool.py ├── handlers │ ├── README.md │ ├── caching-proxy.py │ ├── ip-ok.py │ ├── never404.py │ ├── nooo.py │ ├── randpic.py │ ├── redirect.py │ └── sorry.py ├── hooks │ ├── README.md │ ├── discord-announce.py │ ├── image-noexif.py │ ├── into-the-cache-it-goes.py │ ├── msg-log.py │ ├── notify.py │ ├── notify2.py │ ├── podcast-normalizer.py │ ├── qbittorrent-magnet.py │ ├── reject-extension.py │ ├── reject-mimetype.py │ ├── reloc-by-ext.py │ ├── usb-eject.js │ ├── usb-eject.py │ ├── wget.py │ ├── xiu-sha.py │ └── xiu.py ├── mtag │ ├── README.md │ ├── audio-bpm.py │ ├── audio-key-slicing.py │ ├── audio-key.py │ ├── cksum.py │ ├── exe.py │ ├── file-ext.py │ ├── guestbook-read.py │ ├── guestbook.py │ ├── image-noexif.py │ ├── install-deps.sh │ ├── media-hash.py │ ├── mousepad.py │ ├── rclone-upload.py │ ├── res │ │ ├── twitter-unmute.user.js │ │ ├── yt-ipr.conf │ │ └── yt-ipr.user.js │ ├── sleep.py │ ├── very-bad-idea.py │ ├── vidchk.py │ ├── wget.py │ └── yt-ipr.py ├── partyfuse-streaming.py ├── partyfuse.py ├── partyfuse2.py ├── partyjournal.py ├── prisonparty.sh ├── u2c.py ├── unforget.py ├── up2k.sh └── zmq-recv.py ├── contrib ├── README.md ├── apache │ └── copyparty.conf ├── cfssl.sh ├── copyparty.bat ├── explorer-nothumbs-nofoldertypes.reg ├── flameshot.sh ├── haproxy │ └── copyparty.conf ├── index.html ├── ios │ └── upload-to-copyparty.shortcut ├── ishare.iscu ├── lighttpd │ ├── subdomain.conf │ └── subpath.conf ├── media-osd-bgone.ps1 ├── nginx │ └── copyparty.conf ├── nixos │ └── modules │ │ └── copyparty.nix ├── openrc │ └── copyparty ├── package │ ├── arch │ │ ├── PKGBUILD │ │ ├── copyparty.conf │ │ ├── copyparty.service │ │ ├── index.md │ │ └── prisonparty.service │ └── nix │ │ └── copyparty │ │ ├── default.nix │ │ ├── pin.json │ │ └── update.py ├── plugins │ ├── README.md │ ├── banner.js │ ├── browser-icons.css │ ├── graft-thumbs.js │ ├── meadup.js │ ├── minimal-up2k.html │ ├── minimal-up2k.js │ ├── quickmove.js │ ├── rave.js │ ├── up2k-hook-ytid.js │ └── up2k-hooks.js ├── rc │ └── copyparty ├── send-to-cpp.contextlet.json ├── sharex.sxcu ├── sharex12.sxcu ├── systemd │ ├── cfssl.service │ ├── copyparty.conf │ ├── copyparty.service │ └── prisonparty.service ├── themes │ ├── bsod.css │ └── bsod.png ├── traefik │ └── copyparty.yaml ├── webdav-cfg.bat ├── windows │ └── copyparty-ctmp.bat └── zfs-tune.py ├── copyparty ├── __init__.py ├── __main__.py ├── __version__.py ├── authsrv.py ├── bos │ ├── __init__.py │ ├── bos.py │ └── path.py ├── broker_mp.py ├── broker_mpw.py ├── broker_thr.py ├── broker_util.py ├── cert.py ├── cfg.py ├── dxml.py ├── fsutil.py ├── ftpd.py ├── httpcli.py ├── httpconn.py ├── httpsrv.py ├── ico.py ├── mdns.py ├── metrics.py ├── mtag.py ├── multicast.py ├── pwhash.py ├── res │ ├── __init__.py │ └── insecure.pem ├── smbd.py ├── ssdp.py ├── star.py ├── stolen │ ├── __init__.py │ ├── dnslib │ │ ├── README.md │ │ ├── __init__.py │ │ ├── bimap.py │ │ ├── bit.py │ │ ├── buffer.py │ │ ├── dns.py │ │ ├── label.py │ │ ├── lex.py │ │ └── ranges.py │ ├── ifaddr │ │ ├── README.md │ │ ├── __init__.py │ │ ├── _posix.py │ │ ├── _shared.py │ │ └── _win32.py │ ├── qrcodegen.py │ └── surrogateescape.py ├── sutil.py ├── svchub.py ├── szip.py ├── tcpsrv.py ├── tftpd.py ├── th_cli.py ├── th_srv.py ├── u2idx.py ├── up2k.py ├── util.py └── web │ ├── Makefile │ ├── a │ ├── __init__.py │ ├── partyfuse.py │ ├── u2c.py │ └── webdav-cfg.bat │ ├── baguettebox.js │ ├── browser.css │ ├── browser.html │ ├── browser.js │ ├── browser2.html │ ├── cf.html │ ├── copyparty.gif │ ├── dbg-audio.js │ ├── dd │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── __init__.py │ ├── deps │ └── __init__.py │ ├── iiam.gif │ ├── md.css │ ├── md.html │ ├── md.js │ ├── md2.css │ ├── md2.js │ ├── mde.css │ ├── mde.html │ ├── mde.js │ ├── msg.css │ ├── msg.html │ ├── rups.css │ ├── rups.html │ ├── rups.js │ ├── shares.css │ ├── shares.html │ ├── shares.js │ ├── splash.css │ ├── splash.html │ ├── splash.js │ ├── svcs.html │ ├── svcs.js │ ├── ui.css │ ├── up2k.js │ ├── util.js │ └── w.hash.js ├── docs ├── README.md ├── TODO.md ├── biquad.html ├── bufsize.txt ├── changelog.md ├── chunksizes.py ├── copyparty.d │ ├── foo │ │ ├── another.conf │ │ └── sibling.conf │ └── some.conf ├── cursed-usecases │ └── README.md ├── design.txt ├── devnotes.md ├── example.conf ├── example2.conf ├── examples │ ├── README.md │ ├── docker │ │ ├── basic-docker-compose │ │ │ ├── copyparty.conf │ │ │ └── docker-compose.yml │ │ ├── idp-authelia-traefik │ │ │ ├── README.md │ │ │ ├── authelia │ │ │ │ ├── configuration.yml │ │ │ │ └── users_database.yml │ │ │ ├── cpp │ │ │ │ └── copyparty.conf │ │ │ └── docker-compose.yml │ │ ├── idp-authentik-traefik │ │ │ ├── README.md │ │ │ ├── based-on │ │ │ │ ├── docker-compose-authentik.yml │ │ │ │ └── docker-compose-traefik.yml │ │ │ ├── cpp │ │ │ │ └── copyparty.conf │ │ │ └── docker-compose.yml │ │ ├── idp │ │ │ └── copyparty.conf │ │ └── portainer.md │ └── windows.md ├── hls.html ├── idp.md ├── lics.txt ├── logo.svg ├── multisearch.html ├── music-analysis.sh ├── notes.bat ├── notes.md ├── notes.sh ├── nuitka.txt ├── pretend-youre-qnap.patch ├── protocol-reference.sh ├── rclone.md ├── rice │ ├── README.md │ └── rtl.patch ├── synology-dsm.md ├── tcp-debug.sh ├── unirange.py ├── up2k.txt ├── versus.md └── xff.md ├── flake.lock ├── flake.nix ├── pyproject.toml ├── scripts ├── bench │ └── filehash.sh ├── copyparty-android.sh ├── copyparty-repack.sh ├── deps-docker │ ├── Dockerfile │ ├── Makefile │ ├── busy-mp3.sh │ ├── codemirror.patch │ ├── easymde-ln.patch │ ├── easymde-marked6.patch │ ├── easymde.patch │ ├── genprism.py │ ├── genprism.sh │ ├── markdown-it.patch │ ├── marked-ln.patch │ ├── marked.patch │ ├── mini-fa.css │ ├── mini-fa.sh │ ├── shiftbase.py │ ├── showdown.patch │ └── zopfli.makefile ├── docker │ ├── Dockerfile.ac │ ├── Dockerfile.dj │ ├── Dockerfile.djd │ ├── Dockerfile.djf │ ├── Dockerfile.djff │ ├── Dockerfile.dju │ ├── Dockerfile.im │ ├── Dockerfile.iv │ ├── Dockerfile.min │ ├── Dockerfile.min.pip │ ├── Makefile │ ├── README.md │ ├── base │ │ ├── Dockerfile.zlibng │ │ └── Makefile │ ├── devnotes.md │ ├── innvikler.sh │ └── make.sh ├── fusefuzz.py ├── genlic.py ├── help2html.py ├── help2txt.sh ├── install-githooks.sh ├── lics │ ├── 1.r13 │ ├── 2.r13 │ ├── 3.r13 │ ├── 4.r13 │ ├── README.md │ └── rot.py ├── logpack.sh ├── make-pypi-release.sh ├── make-pyz.sh ├── make-sfx.sh ├── make-tgz-release.sh ├── prep.sh ├── profile.py ├── py2 │ └── queue │ │ └── __init__.py ├── pyinstaller │ ├── README.md │ ├── build.sh │ ├── depchk.sh │ ├── deps.sha512 │ ├── deps.txt │ ├── ffmpeg.patch │ ├── ffmpeg.txt │ ├── icon.sh │ ├── loader.py │ ├── loader.rc │ ├── notes.txt │ ├── up2k.rc │ ├── up2k.sh │ ├── up2k.spec │ └── up2k.spec.sh ├── rls.sh ├── run-tests.sh ├── sfx.ls ├── sfx.py ├── sfx.sh ├── speedtest-fs.py ├── strip_hints │ └── a.py ├── test │ ├── ptrav.py │ ├── race.py │ ├── smoketest.py │ └── tftp.sh ├── tl.js ├── tl.py ├── tlcheck.sh ├── toc.sh ├── uncomment.py └── ziploader.py ├── setup.py ├── srv ├── ceditable.html ├── chat.md ├── expand │ └── README.md ├── extend.md ├── ping.html └── test.md └── tests ├── __init__.py ├── ptrav.py ├── res └── idp │ ├── 1.conf │ ├── 2.conf │ ├── 3.conf │ ├── 4.conf │ ├── 5.conf │ ├── 6.conf │ └── 7.conf ├── run.py ├── test_cp.py ├── test_dedup.py ├── test_dots.py ├── test_dxml.py ├── test_hooks.py ├── test_httpcli.py ├── test_idp.py ├── test_metrics.py ├── test_mv.py ├── test_utils.py ├── test_vfs.py ├── test_webdav.py └── util.py /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 12 9 | }, 10 | "rules": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.reg text eol=crlf 4 | 5 | *.png binary 6 | *.gif binary 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '9001' 7 | 8 | --- 9 | 10 | NOTE: 11 | **please use english, or include an english translation.** aside from that, 12 | all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md 13 | 14 | 15 | ### Describe the bug 16 | a description of what the bug is 17 | 18 | ### To Reproduce 19 | List of steps to reproduce the issue, or, if it's hard to reproduce, then at least a detailed explanation of what you did to run into it 20 | 21 | ### Expected behavior 22 | a description of what you expected to happen 23 | 24 | ### Screenshots 25 | if applicable, add screenshots to help explain your problem, such as the kickass crashpage :^) 26 | 27 | ### Server details (if you are using docker/podman) 28 | remove the ones that are not relevant: 29 | * **server OS / version:** 30 | * **how you're running copyparty:** (docker/podman/something-else) 31 | * **docker image:** (variant, version, and arch if you know) 32 | * **copyparty arguments and/or config-file:** 33 | 34 | ### Server details (if you're NOT using docker/podman) 35 | remove the ones that are not relevant: 36 | * **server OS / version:** 37 | * **what copyparty did you grab:** (sfx/exe/pip/arch/...) 38 | * **how you're running it:** (in a terminal, as a systemd-service, ...) 39 | * run copyparty with `--version` and grab the last 3 lines (they start with `copyparty`, `CPython`, `sqlite`) and paste them below this line: 40 | * **copyparty arguments and/or config-file:** 41 | 42 | ### Client details 43 | if the issue is possibly on the client-side, then mention some of the following: 44 | * the device type and model: 45 | * OS version: 46 | * browser version: 47 | 48 | ### Additional context 49 | any other context about the problem here 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '9001' 7 | 8 | --- 9 | 10 | NOTE: 11 | **please use english, or include an english translation.** aside from that, 12 | all of the below are optional, consider them as inspiration, delete and rewrite at will 13 | 14 | **is your feature request related to a problem? Please describe.** 15 | a description of what the problem is, for example, `I'm always frustrated when [...]` or `Why is it not possible to [...]` 16 | 17 | **Describe the idea / solution you'd like** 18 | a description of what you want to happen 19 | 20 | **Describe any alternatives you've considered** 21 | a description of any alternative solutions or features you've considered 22 | 23 | **Additional context** 24 | add any other context or screenshots about the feature request here 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something else 3 | about: "┐(゚∀゚)┌" 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/branch-rename.md: -------------------------------------------------------------------------------- 1 | modernize your local checkout of the repo like so, 2 | ```sh 3 | git branch -m master hovudstraum 4 | git fetch origin 5 | git branch -u origin/hovudstraum hovudstraum 6 | git remote set-head origin -a 7 | ``` 8 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | To show that your contribution is compatible with the MIT License, please include the following text somewhere in this PR description: 2 | This PR complies with the DCO; https://developercertificate.org/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | MANIFEST.in 6 | MANIFEST 7 | copyparty.egg-info/ 8 | .venv/ 9 | 10 | /buildenv/ 11 | /build/ 12 | /dist/ 13 | /py2/ 14 | /sfx* 15 | /pyz/ 16 | /unt/ 17 | /log/ 18 | 19 | # ide 20 | *.sublime-workspace 21 | 22 | # winmerge 23 | *.bak 24 | 25 | # apple pls 26 | .DS_Store 27 | 28 | # derived 29 | copyparty/res/COPYING.txt 30 | copyparty/web/deps/ 31 | srv/ 32 | scripts/docker/i/ 33 | scripts/deps-docker/uncomment.py 34 | contrib/package/arch/pkg/ 35 | contrib/package/arch/src/ 36 | 37 | # state/logs 38 | up.*.txt 39 | .hist/ 40 | scripts/docker/*.out 41 | scripts/docker/*.err 42 | /perf.* 43 | 44 | # nix build output link 45 | result 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run copyparty", 6 | "type": "python", 7 | "request": "launch", 8 | "module": "copyparty", 9 | "console": "integratedTerminal", 10 | "cwd": "${workspaceFolder}", 11 | "justMyCode": false, 12 | "env": { 13 | "PYDEVD_DISABLE_FILE_VALIDATION": "1", 14 | "PYTHONWARNINGS": "always", //error 15 | }, 16 | "args": [ 17 | //"-nw", 18 | "-ed", 19 | "-emp", 20 | "-e2dsa", 21 | "-e2ts", 22 | "-mtp=.bpm=f,bin/mtag/audio-bpm.py", 23 | "-aed:wark", 24 | "-vsrv::r:rw,ed:c,dupe", 25 | "-vdist:dist:r" 26 | ] 27 | }, 28 | { 29 | "name": "No debug", 30 | "preLaunchTask": "no_dbg", 31 | "type": "python", 32 | //"request": "attach", "port": 42069 33 | // fork: nc -l 42069 1 and os.path.isfile(sys.argv[1]): 35 | sfx = sys.argv[1] 36 | sys.argv = [sys.argv[0]] + sys.argv[2:] 37 | 38 | argv += sys.argv[1:] 39 | 40 | if sfx: 41 | argv = [sys.executable, sfx] + argv 42 | sp.check_call(argv) 43 | elif re.search(" -j ?[0-9]", " ".join(argv)): 44 | argv = [sys.executable, "-Wa", "-m", "copyparty"] + argv 45 | sp.check_call(argv) 46 | else: 47 | sys.path.insert(0, os.getcwd()) 48 | from copyparty.__main__ import main as copyparty 49 | 50 | try: 51 | copyparty(["a"] + argv) 52 | except SystemExit as ex: 53 | if ex.code: 54 | raise 55 | 56 | print("\n\033[32mokke\033[0m") 57 | sys.exit(1) 58 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | // https://ocv.me/dot/bifrost.html 4 | "terminal.background": "#1e1e1e", 5 | "terminal.foreground": "#d2d2d2", 6 | "terminalCursor.background": "#93A1A1", 7 | "terminalCursor.foreground": "#93A1A1", 8 | "terminal.ansiBlack": "#404040", 9 | "terminal.ansiRed": "#f03669", 10 | "terminal.ansiGreen": "#b8e346", 11 | "terminal.ansiYellow": "#ffa402", 12 | "terminal.ansiBlue": "#02a2ff", 13 | "terminal.ansiMagenta": "#f65be3", 14 | "terminal.ansiCyan": "#3da698", 15 | "terminal.ansiWhite": "#d2d2d2", 16 | "terminal.ansiBrightBlack": "#606060", 17 | "terminal.ansiBrightRed": "#c75b79", 18 | "terminal.ansiBrightGreen": "#c8e37e", 19 | "terminal.ansiBrightYellow": "#ffbe4a", 20 | "terminal.ansiBrightBlue": "#71cbff", 21 | "terminal.ansiBrightMagenta": "#b67fe3", 22 | "terminal.ansiBrightCyan": "#9cf0ed", 23 | "terminal.ansiBrightWhite": "#ffffff", 24 | }, 25 | "python.terminal.activateEnvironment": false, 26 | "python.analysis.enablePytestSupport": false, 27 | "python.analysis.typeCheckingMode": "standard", 28 | "python.testing.pytestEnabled": false, 29 | "python.testing.unittestEnabled": true, 30 | "python.testing.unittestArgs": [ 31 | "-v", 32 | "-s", 33 | "./tests", 34 | "-p", 35 | "test_*.py" 36 | ], 37 | // python3 -m isort --py=27 --profile=black ~/dev/copyparty/{copyparty,tests}/*.py && python3 -m black -t py27 ~/dev/copyparty/{copyparty,tests,bin}/*.py $(find ~/dev/copyparty/copyparty/stolen -iname '*.py') 38 | "editor.formatOnSave": false, 39 | "[html]": { 40 | "editor.formatOnSave": false, 41 | "editor.autoIndent": "keep", 42 | }, 43 | "[css]": { 44 | "editor.formatOnSave": false, 45 | }, 46 | "files.associations": { 47 | "*.makefile": "makefile" 48 | }, 49 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "pre", 6 | "command": "true;rm -rf inc/* inc/.hist/;mkdir -p inc;", 7 | "type": "shell" 8 | }, 9 | { 10 | "label": "no_dbg", 11 | "type": "shell", 12 | "command": "${config:python.pythonPath}", 13 | "args": [ 14 | "-Wa", //-We 15 | ".vscode/launch.py" 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | in the words of Abraham Lincoln: 2 | 3 | > Be excellent to each other... and... PARTY ON, DUDES! 4 | 5 | more specifically I'll paraphrase some examples from a german automotive corporation as they cover all the bases without being too wordy 6 | 7 | ## Examples of unacceptable behavior 8 | * intimidation, harassment, trolling 9 | * insulting, derogatory, harmful or prejudicial comments 10 | * posting private information without permission 11 | * political or personal attacks 12 | 13 | ## Examples of expected behavior 14 | * being nice, friendly, welcoming, inclusive, mindful and empathetic 15 | * acting considerate, modest, respectful 16 | * using polite and inclusive language 17 | * criticize constructively and accept constructive criticism 18 | * respect different points of view 19 | 20 | ## finally and even more specifically, 21 | * parse opinions and feedback objectively without prejudice 22 | * it's the message that matters, not who said it 23 | 24 | aaand that's how you say `be nice` in a way that fills half a floppy w 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | if you hit something extra juicy pls let me know on either of the following 4 | * email -- `copyparty@ocv.ze` except `ze` should be `me` 5 | * [mastodon dm](https://layer8.space/@tripflag) -- `@tripflag@layer8.space` 6 | * [github private vulnerability report](https://github.com/9001/copyparty/security/advisories/new), wow that form is complicated 7 | * [twitter dm](https://twitter.com/tripflag) (if im somehow not banned yet) 8 | 9 | no bug bounties sorry! all i can offer is greetz in the release notes 10 | -------------------------------------------------------------------------------- /bin/bubbleparty.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # usage: ./bubbleparty.sh ./copyparty-sfx.py .... 3 | bwrap \ 4 | --unshare-all \ 5 | --ro-bind /usr /usr \ 6 | --ro-bind /bin /bin \ 7 | --ro-bind /lib /lib \ 8 | --ro-bind /etc/resolv.conf /etc/resolv.conf \ 9 | --dev-bind /dev /dev \ 10 | --dir /tmp \ 11 | --dir /var \ 12 | --bind $(pwd) $(pwd) \ 13 | --share-net \ 14 | --die-with-parent \ 15 | --file 11 /etc/passwd \ 16 | --file 12 /etc/group \ 17 | "$@" \ 18 | 11< <(getent passwd $(id -u) 65534) \ 19 | 12< <(getent group $(id -g) 65534) 20 | -------------------------------------------------------------------------------- /bin/handlers/README.md: -------------------------------------------------------------------------------- 1 | replace the standard 404 / 403 responses with plugins 2 | 3 | 4 | # usage 5 | 6 | load plugins either globally with `--on404 ~/dev/copyparty/bin/handlers/sorry.py` or for a specific volume with `:c,on404=~/handlers/sorry.py` 7 | 8 | 9 | # api 10 | 11 | each plugin must define a `main()` which takes 3 arguments; 12 | 13 | * `cli` is an instance of [copyparty/httpcli.py](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/httpcli.py) (the monstrosity itself) 14 | * `vn` is the VFS which overlaps with the requested URL, and 15 | * `rem` is the URL remainder below the VFS mountpoint 16 | * so `vn.vpath + rem` == `cli.vpath` == original request 17 | 18 | 19 | # examples 20 | 21 | ## on404 22 | 23 | * [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file 24 | * [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/` 25 | * [sorry.py](answer.py) replies with a custom message instead of the usual 404 26 | * [nooo.py](nooo.py) replies with an endless noooooooooooooo 27 | * [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary 28 | * [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff 29 | 30 | ## on403 31 | 32 | * [ip-ok.py](ip-ok.py) disables security checks if client-ip is 1.2.3.4 33 | 34 | 35 | # notes 36 | 37 | * on403 only works for trivial stuff (basic http access) since I haven't been able to think of any good usecases for it (was just easy to add while doing on404) 38 | -------------------------------------------------------------------------------- /bin/handlers/caching-proxy.py: -------------------------------------------------------------------------------- 1 | # assume each requested file exists on another webserver and 2 | # download + mirror them as they're requested 3 | # (basically pretend we're warnish) 4 | 5 | import os 6 | import requests 7 | 8 | from typing import TYPE_CHECKING 9 | 10 | if TYPE_CHECKING: 11 | from copyparty.httpcli import HttpCli 12 | 13 | 14 | def main(cli: "HttpCli", vn, rem): 15 | url = "https://mirrors.edge.kernel.org/alpine/" + rem 16 | abspath = os.path.join(vn.realpath, rem) 17 | 18 | # sneaky trick to preserve a requests-session between downloads 19 | # so it doesn't have to spend ages reopening https connections; 20 | # luckily we can stash it inside the copyparty client session, 21 | # name just has to be definitely unused so "hacapo_req_s" it is 22 | req_s = getattr(cli.conn, "hacapo_req_s", None) or requests.Session() 23 | setattr(cli.conn, "hacapo_req_s", req_s) 24 | 25 | try: 26 | os.makedirs(os.path.dirname(abspath), exist_ok=True) 27 | with req_s.get(url, stream=True, timeout=69) as r: 28 | r.raise_for_status() 29 | with open(abspath, "wb", 64 * 1024) as f: 30 | for buf in r.iter_content(chunk_size=64 * 1024): 31 | f.write(buf) 32 | except: 33 | os.unlink(abspath) 34 | return "false" 35 | 36 | return "retry" 37 | -------------------------------------------------------------------------------- /bin/handlers/ip-ok.py: -------------------------------------------------------------------------------- 1 | # disable permission checks and allow access if client-ip is 1.2.3.4 2 | 3 | 4 | def main(cli, vn, rem): 5 | if cli.ip == "1.2.3.4": 6 | return "allow" 7 | -------------------------------------------------------------------------------- /bin/handlers/never404.py: -------------------------------------------------------------------------------- 1 | # create a dummy file and let copyparty return it 2 | 3 | 4 | def main(cli, vn, rem): 5 | print("hello", cli.ip) 6 | 7 | abspath = vn.canonical(rem) 8 | with open(abspath, "wb") as f: 9 | f.write(b"404? not on MY watch!") 10 | 11 | return "retry" 12 | -------------------------------------------------------------------------------- /bin/handlers/nooo.py: -------------------------------------------------------------------------------- 1 | # reply with an endless "noooooooooooooooooooooooo" 2 | 3 | 4 | def say_no(): 5 | yield b"n" 6 | while True: 7 | yield b"o" * 4096 8 | 9 | 10 | def main(cli, vn, rem): 11 | cli.send_headers(None, 404, "text/plain") 12 | 13 | for chunk in say_no(): 14 | cli.s.sendall(chunk) 15 | 16 | return "false" 17 | -------------------------------------------------------------------------------- /bin/handlers/randpic.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | from urllib.parse import quote 4 | 5 | 6 | # assuming /foo/bar/ is a valid URL but /foo/bar/randpic.png does not exist, 7 | # hijack the 404 with a redirect to a random pic in that folder 8 | # 9 | # thx to lia & kipu for the idea 10 | 11 | 12 | def main(cli, vn, rem): 13 | req_fn = rem.split("/")[-1] 14 | if not cli.can_read or not req_fn.startswith("randpic"): 15 | return 16 | 17 | req_abspath = vn.canonical(rem) 18 | req_ap_dir = os.path.dirname(req_abspath) 19 | files_in_dir = os.listdir(req_ap_dir) 20 | 21 | if "." in req_fn: 22 | file_ext = "." + req_fn.split(".")[-1] 23 | files_in_dir = [x for x in files_in_dir if x.lower().endswith(file_ext)] 24 | 25 | if not files_in_dir: 26 | return 27 | 28 | selected_file = random.choice(files_in_dir) 29 | 30 | req_url = "/".join([vn.vpath, rem]).strip("/") 31 | req_dir = req_url.rsplit("/", 1)[0] 32 | new_url = "/".join([req_dir, quote(selected_file)]).strip("/") 33 | 34 | cli.reply(b"redirecting...", 302, headers={"Location": "/" + new_url}) 35 | return "true" 36 | -------------------------------------------------------------------------------- /bin/handlers/redirect.py: -------------------------------------------------------------------------------- 1 | # if someone hits a 404, redirect them to another location 2 | 3 | 4 | def send_http_302_temporary_redirect(cli, new_path): 5 | """ 6 | replies with an HTTP 302, which is a temporary redirect; 7 | "new_path" can be any of the following: 8 | - "http://a.com/" would redirect to another website, 9 | - "/foo/bar" would redirect to /foo/bar on the same server; 10 | note the leading '/' in the location which is important 11 | """ 12 | cli.reply(b"redirecting...", 302, headers={"Location": new_path}) 13 | 14 | 15 | def send_http_301_permanent_redirect(cli, new_path): 16 | """ 17 | replies with an HTTP 301, which is a permanent redirect; 18 | otherwise identical to send_http_302_temporary_redirect 19 | """ 20 | cli.reply(b"redirecting...", 301, headers={"Location": new_path}) 21 | 22 | 23 | def send_errorpage_with_redirect_link(cli, new_path): 24 | """ 25 | replies with a website explaining that the page has moved; 26 | "new_path" must be an absolute location on the same server 27 | but without a leading '/', so for example "foo/bar" 28 | would redirect to "/foo/bar" 29 | """ 30 | cli.redirect(new_path, click=False, msg="this page has moved") 31 | 32 | 33 | def main(cli, vn, rem): 34 | """ 35 | this is the function that gets called by copyparty; 36 | note that vn.vpath and cli.vpath does not have a leading '/' 37 | so we're adding the slash in the debug messages below 38 | """ 39 | print(f"this client just hit a 404: {cli.ip}") 40 | print(f"they were accessing this volume: /{vn.vpath}") 41 | print(f"and the original request-path (straight from the URL) was /{cli.vpath}") 42 | print(f"...which resolves to the following filesystem path: {vn.canonical(rem)}") 43 | 44 | new_path = "/foo/bar/" 45 | print(f"will now redirect the client to {new_path}") 46 | 47 | # uncomment one of these: 48 | send_http_302_temporary_redirect(cli, new_path) 49 | #send_http_301_permanent_redirect(cli, new_path) 50 | #send_errorpage_with_redirect_link(cli, new_path) 51 | 52 | return "true" 53 | -------------------------------------------------------------------------------- /bin/handlers/sorry.py: -------------------------------------------------------------------------------- 1 | # sends a custom response instead of the usual 404 2 | 3 | 4 | def main(cli, vn, rem): 5 | msg = f"sorry {cli.ip} but {cli.vpath} doesn't exist" 6 | 7 | return str(cli.reply(msg.encode("utf-8"), 404, "text/plain")) 8 | -------------------------------------------------------------------------------- /bin/hooks/image-noexif.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess as sp 6 | 7 | 8 | _ = r""" 9 | remove exif tags from uploaded images; the eventhook edition of 10 | https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/image-noexif.py 11 | 12 | dependencies: 13 | exiftool / perl-Image-ExifTool 14 | 15 | being an upload hook, this will take effect after upload completion 16 | but before copyparty has hashed/indexed the file, which means that 17 | copyparty will never index the original file, so deduplication will 18 | not work as expected... which is mostly OK but ehhh 19 | 20 | note: modifies the file in-place, so don't set the `f` (fork) flag 21 | 22 | example usages; either as global config (all volumes) or as volflag: 23 | --xau bin/hooks/image-noexif.py 24 | -v srv/inc:inc:r:rw,ed:c,xau=bin/hooks/image-noexif.py 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | explained: 28 | share fs-path srv/inc at /inc (readable by all, read-write for user ed) 29 | running this xau (execute-after-upload) plugin for all uploaded files 30 | """ 31 | 32 | 33 | # filetypes to process; ignores everything else 34 | EXTS = ("jpg", "jpeg", "avif", "heif", "heic") 35 | 36 | 37 | try: 38 | from copyparty.util import fsenc 39 | except: 40 | 41 | def fsenc(p): 42 | return p.encode("utf-8") 43 | 44 | 45 | def main(): 46 | fp = sys.argv[1] 47 | ext = fp.lower().split(".")[-1] 48 | if ext not in EXTS: 49 | return 50 | 51 | cwd, fn = os.path.split(fp) 52 | os.chdir(cwd) 53 | f1 = fsenc(fn) 54 | cmd = [ 55 | b"exiftool", 56 | b"-exif:all=", 57 | b"-iptc:all=", 58 | b"-xmp:all=", 59 | b"-P", 60 | b"-overwrite_original", 61 | b"--", 62 | f1, 63 | ] 64 | sp.check_output(cmd) 65 | print("image-noexif: stripped") 66 | 67 | 68 | if __name__ == "__main__": 69 | try: 70 | main() 71 | except: 72 | pass 73 | -------------------------------------------------------------------------------- /bin/hooks/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess as sp 6 | from plyer import notification 7 | 8 | 9 | _ = r""" 10 | show os notification on upload; works on windows, linux, macos, android 11 | 12 | depdencies: 13 | windows: python3 -m pip install --user -U plyer 14 | linux: python3 -m pip install --user -U plyer 15 | macos: python3 -m pip install --user -U plyer pyobjus 16 | android: just termux and termux-api 17 | 18 | example usages; either as global config (all volumes) or as volflag: 19 | --xau f,bin/hooks/notify.py 20 | -v srv/inc:inc:r:rw,ed:c,xau=f,bin/hooks/notify.py 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | (share filesystem-path srv/inc as volume /inc, 24 | readable by everyone, read-write for user 'ed', 25 | running this plugin on all uploads with the params listed below) 26 | 27 | parameters explained, 28 | xau = execute after upload 29 | f = fork so it doesn't block uploads 30 | """ 31 | 32 | 33 | try: 34 | from copyparty.util import humansize 35 | except: 36 | 37 | def humansize(n): 38 | return n 39 | 40 | 41 | def main(): 42 | fp = sys.argv[1] 43 | dp, fn = os.path.split(fp) 44 | try: 45 | sz = humansize(os.path.getsize(fp)) 46 | except: 47 | sz = "?" 48 | 49 | msg = "{} ({})\n📁 {}".format(fn, sz, dp) 50 | title = "File received" 51 | 52 | if "com.termux" in sys.executable: 53 | sp.run(["termux-notification", "-t", title, "-c", msg]) 54 | return 55 | 56 | icon = "emblem-documents-symbolic" if sys.platform == "linux" else "" 57 | notification.notify( 58 | title=title, 59 | message=msg, 60 | app_icon=icon, 61 | timeout=10, 62 | ) 63 | 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /bin/hooks/notify2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import os 5 | import sys 6 | import subprocess as sp 7 | from datetime import datetime, timezone 8 | from plyer import notification 9 | 10 | 11 | _ = r""" 12 | same as notify.py but with additional info (uploader, ...) 13 | and also supports --xm (notify on 📟 message) 14 | 15 | example usages; either as global config (all volumes) or as volflag: 16 | --xm f,j,bin/hooks/notify2.py 17 | --xau f,j,bin/hooks/notify2.py 18 | -v srv/inc:inc:r:rw,ed:c,xm=f,j,bin/hooks/notify2.py 19 | -v srv/inc:inc:r:rw,ed:c,xau=f,j,bin/hooks/notify2.py 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | 22 | (share filesystem-path srv/inc as volume /inc, 23 | readable by everyone, read-write for user 'ed', 24 | running this plugin on all uploads / msgs with the params listed below) 25 | 26 | parameters explained, 27 | xau = execute after upload 28 | f = fork so it doesn't block uploads 29 | j = provide json instead of filepath list 30 | """ 31 | 32 | 33 | try: 34 | from copyparty.util import humansize 35 | except: 36 | 37 | def humansize(n): 38 | return n 39 | 40 | 41 | def main(): 42 | inf = json.loads(sys.argv[1]) 43 | fp = inf["ap"] 44 | sz = humansize(inf["sz"]) 45 | dp, fn = os.path.split(fp) 46 | dt = datetime.fromtimestamp(inf["mt"], timezone.utc) 47 | mt = dt.strftime("%Y-%m-%d %H:%M:%S") 48 | 49 | msg = f"{fn} ({sz})\n📁 {dp}" 50 | title = "File received" 51 | icon = "emblem-documents-symbolic" if sys.platform == "linux" else "" 52 | 53 | if inf.get("txt"): 54 | msg = inf["txt"] 55 | title = "Message received" 56 | icon = "mail-unread-symbolic" if sys.platform == "linux" else "" 57 | 58 | msg += f"\n👤 {inf['user']} ({inf['ip']})\n🕒 {mt}" 59 | 60 | if "com.termux" in sys.executable: 61 | sp.run(["termux-notification", "-t", title, "-c", msg]) 62 | return 63 | 64 | notification.notify( 65 | title=title, 66 | message=msg, 67 | app_icon=icon, 68 | timeout=10, 69 | ) 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /bin/hooks/reject-extension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | _ = r""" 7 | reject file uploads by file extension 8 | 9 | example usage as global config: 10 | --xbu c,bin/hooks/reject-extension.py 11 | 12 | example usage as a volflag (per-volume config): 13 | -v srv/inc:inc:r:rw,ed:c,xbu=c,bin/hooks/reject-extension.py 14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | 16 | (share filesystem-path srv/inc as volume /inc, 17 | readable by everyone, read-write for user 'ed', 18 | running this plugin on all uploads with the params listed below) 19 | 20 | parameters explained, 21 | xbu = execute before upload 22 | c = check result, reject upload if error 23 | """ 24 | 25 | 26 | def main(): 27 | bad = "exe scr com pif bat ps1 jar msi" 28 | 29 | ext = sys.argv[1].split(".")[-1] 30 | 31 | sys.exit(1 if ext in bad.split() else 0) 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /bin/hooks/reject-mimetype.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import magic 5 | 6 | 7 | _ = r""" 8 | reject file uploads by mimetype 9 | 10 | dependencies (linux, macos): 11 | python3 -m pip install --user -U python-magic 12 | 13 | dependencies (windows): 14 | python3 -m pip install --user -U python-magic-bin 15 | 16 | example usage as global config: 17 | --xau c,bin/hooks/reject-mimetype.py 18 | 19 | example usage as a volflag (per-volume config): 20 | -v srv/inc:inc:r:rw,ed:c,xau=c,bin/hooks/reject-mimetype.py 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | (share filesystem-path srv/inc as volume /inc, 24 | readable by everyone, read-write for user 'ed', 25 | running this plugin on all uploads with the params listed below) 26 | 27 | parameters explained, 28 | xau = execute after upload 29 | c = check result, reject upload if error 30 | """ 31 | 32 | 33 | def main(): 34 | ok = ["image/jpeg", "image/png"] 35 | 36 | mt = magic.from_file(sys.argv[1], mime=True) 37 | 38 | print(mt) 39 | 40 | sys.exit(1 if mt not in ok else 0) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /bin/hooks/usb-eject.js: -------------------------------------------------------------------------------- 1 | // see usb-eject.py for usage 2 | 3 | function usbclick() { 4 | var o = QS('#treeul a[dst="/usb/"]') || QS('#treepar a[dst="/usb/"]'); 5 | if (o) 6 | o.click(); 7 | } 8 | 9 | function eject_cb() { 10 | var t = ('' + this.responseText).trim(); 11 | if (t.indexOf('can be safely unplugged') < 0 && t.indexOf('Device can be removed') < 0) 12 | return toast.err(30, 'usb eject failed:\n\n' + t); 13 | 14 | toast.ok(5, esc(t.replace(/ - /g, '\n\n')).trim()); 15 | usbclick(); setTimeout(usbclick, 10); 16 | }; 17 | 18 | function add_eject_2(a) { 19 | var aw = a.getAttribute('href').split(/\//g); 20 | if (aw.length != 4 || aw[3]) 21 | return; 22 | 23 | var v = aw[2], 24 | k = 'umount_' + v; 25 | 26 | for (var b = 0; b < 9; b++) { 27 | var o = ebi(k); 28 | if (!o) 29 | break; 30 | o.parentNode.removeChild(o); 31 | } 32 | 33 | a.appendChild(mknod('span', k, '⏏'), a); 34 | o = ebi(k); 35 | o.style.cssText = 'position:absolute; right:1em; margin-top:-.2em; font-size:1.3em'; 36 | o.onclick = function (e) { 37 | ev(e); 38 | var xhr = new XHR(); 39 | xhr.open('POST', get_evpath(), true); 40 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); 41 | xhr.send('msg=' + uricom_enc(':usb-eject:' + v + ':')); 42 | xhr.onload = xhr.onerror = eject_cb; 43 | toast.inf(10, "ejecting " + v + "..."); 44 | }; 45 | }; 46 | 47 | function add_eject() { 48 | var o = QSA('#treeul a[href^="/usb/"]') || QSA('#treepar a[href^="/usb/"]'); 49 | for (var a = o.length - 1; a > 0; a--) 50 | add_eject_2(o[a]); 51 | }; 52 | 53 | (function() { 54 | var f0 = treectl.rendertree; 55 | treectl.rendertree = function (res, ts, top0, dst, rst) { 56 | var ret = f0(res, ts, top0, dst, rst); 57 | add_eject(); 58 | return ret; 59 | }; 60 | })(); 61 | 62 | setTimeout(add_eject, 50); 63 | -------------------------------------------------------------------------------- /bin/hooks/usb-eject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import stat 5 | import subprocess as sp 6 | import sys 7 | 8 | 9 | """ 10 | if you've found yourself using copyparty to serve flashdrives on a LAN 11 | and your only wish is that the web-UI had a button to unmount / safely 12 | remove those flashdrives, then boy howdy are you in the right place :D 13 | 14 | put usb-eject.js in the webroot (or somewhere else http-accessible) 15 | then run copyparty with these args: 16 | 17 | -v /run/media/egon:/usb:A:c,hist=/tmp/junk 18 | --xm=c1,bin/hooks/usb-eject.py 19 | --js-browser=/usb-eject.js 20 | 21 | which does the following respectively, 22 | 23 | * share all of /run/media/egon as /usb with admin for everyone 24 | and put the histpath somewhere it won't cause trouble 25 | * run the usb-eject hook with stdout redirect to the web-ui 26 | * add the complementary usb-eject.js to the browser 27 | 28 | """ 29 | 30 | 31 | def main(): 32 | try: 33 | label = sys.argv[1].split(":usb-eject:")[1].split(":")[0] 34 | mp = "/run/media/egon/" + label 35 | # print("ejecting [%s]... " % (mp,), end="") 36 | mp = os.path.abspath(os.path.realpath(mp.encode("utf-8"))) 37 | st = os.lstat(mp) 38 | if not stat.S_ISDIR(st.st_mode): 39 | raise Exception("not a regular directory") 40 | 41 | # if you're running copyparty as root (thx for the faith) 42 | # you'll need something like this to make dbus talkative 43 | cmd = b"sudo -u egon DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus gio mount -e" 44 | 45 | # but if copyparty and the ui-session is running 46 | # as the same user (good) then this is plenty 47 | cmd = b"gio mount -e" 48 | 49 | cmd = cmd.split(b" ") + [mp] 50 | ret = sp.check_output(cmd).decode("utf-8", "replace") 51 | print(ret.strip() or (label + " can be safely unplugged")) 52 | 53 | except Exception as ex: 54 | print("unmount failed: %r" % (ex,)) 55 | 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /bin/hooks/wget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import json 6 | import subprocess as sp 7 | 8 | 9 | _ = r""" 10 | use copyparty as a file downloader by POSTing URLs as 11 | application/x-www-form-urlencoded (for example using the 12 | 📟 message-to-server-log in the web-ui) 13 | 14 | example usage as global config: 15 | --xm aw,f,j,t3600,bin/hooks/wget.py 16 | 17 | parameters explained, 18 | xm = execute on message-to-server-log 19 | aw = only users with write-access can use this 20 | f = fork; don't delay other hooks while this is running 21 | j = provide message information as json (not just the text) 22 | c3 = mute all output 23 | t3600 = timeout and abort download after 1 hour 24 | 25 | example usage as a volflag (per-volume config): 26 | -v srv/inc:inc:r:rw,ed:c,xm=aw,f,j,t3600,bin/hooks/wget.py 27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | 29 | (share filesystem-path srv/inc as volume /inc, 30 | readable by everyone, read-write for user 'ed', 31 | running this plugin on all messages with the params explained above) 32 | 33 | example usage as a volflag in a copyparty config file: 34 | [/inc] 35 | srv/inc 36 | accs: 37 | r: * 38 | rw: ed 39 | flags: 40 | xm: aw,f,j,t3600,bin/hooks/wget.py 41 | 42 | the volflag examples only kicks in if you send the message 43 | while you're in the /inc folder (or any folder below there) 44 | """ 45 | 46 | 47 | def main(): 48 | inf = json.loads(sys.argv[1]) 49 | url = inf["txt"] 50 | if "://" not in url: 51 | url = "https://" + url 52 | 53 | proto = url.split("://")[0].lower() 54 | if proto not in ("http", "https", "ftp", "ftps"): 55 | raise Exception("bad proto {}".format(proto)) 56 | 57 | os.chdir(inf["ap"]) 58 | 59 | name = url.split("?")[0].split("/")[-1] 60 | tfn = "-- DOWNLOADING " + name 61 | print(f"{tfn}\n", end="") 62 | open(tfn, "wb").close() 63 | 64 | cmd = ["wget", "--trust-server-names", "-nv", "--", url] 65 | 66 | try: 67 | sp.check_call(cmd) 68 | except: 69 | t = "-- FAILED TO DONWLOAD " + name 70 | print(f"{t}\n", end="") 71 | open(t, "wb").close() 72 | 73 | os.unlink(tfn) 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /bin/hooks/xiu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import sys 5 | 6 | 7 | _ = r""" 8 | this hook prints absolute filepaths + total size 9 | 10 | use this with --xiu, which makes copyparty buffer 11 | uploads until server is idle, providing file infos 12 | on stdin (filepaths or json) 13 | 14 | example usage as global config: 15 | --xiu i1,j,bin/hooks/xiu.py 16 | 17 | example usage as a volflag (per-volume config): 18 | -v srv/inc:inc:r:rw,ed:c,xiu=i1,j,bin/hooks/xiu.py 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | (share filesystem-path srv/inc as volume /inc, 22 | readable by everyone, read-write for user 'ed', 23 | running this plugin on batches of uploads with the params listed below) 24 | 25 | parameters explained, 26 | xiu = execute after uploads... 27 | i1 = ...after volume has been idle for 1sec 28 | j = provide json instead of filepath list 29 | 30 | note the "f" (fork) flag is not set, so this xiu 31 | will block other xiu hooks while it's running 32 | """ 33 | 34 | 35 | def main(): 36 | zb = sys.stdin.buffer.read() 37 | zs = zb.decode("utf-8", "replace") 38 | inf = json.loads(zs) 39 | 40 | total_sz = 0 41 | for upload in inf: 42 | sz = upload["sz"] 43 | total_sz += sz 44 | print("{:9} {}".format(sz, upload["ap"])) 45 | 46 | print("{} files, {} bytes total".format(len(inf), total_sz)) 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /bin/mtag/audio-key.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import tempfile 6 | import subprocess as sp 7 | import keyfinder 8 | 9 | from copyparty.util import fsenc 10 | 11 | """ 12 | dep: github/mixxxdj/libkeyfinder 13 | dep: pypi/keyfinder 14 | dep: ffmpeg 15 | """ 16 | 17 | 18 | # tried trimming the first/last 5th, bad idea, 19 | # misdetects 9a law field (Sphere Caliber) as 10b, 20 | # obvious when mixing 9a ghostly parapara ship 21 | 22 | 23 | def det(tf): 24 | # fmt: off 25 | sp.check_call([ 26 | b"ffmpeg", 27 | b"-nostdin", 28 | b"-hide_banner", 29 | b"-v", b"fatal", 30 | b"-y", b"-i", fsenc(sys.argv[1]), 31 | b"-map", b"0:a:0", 32 | b"-t", b"300", 33 | b"-sample_fmt", b"s16", 34 | fsenc(tf) 35 | ]) 36 | # fmt: on 37 | 38 | print(keyfinder.key(tf).camelot()) 39 | 40 | 41 | def main(): 42 | with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f: 43 | f.write(b"h") 44 | tf = f.name 45 | 46 | try: 47 | det(tf) 48 | except: 49 | pass # mute 50 | finally: 51 | os.unlink(tf) 52 | 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /bin/mtag/file-ext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | """ 6 | example that just prints the file extension 7 | """ 8 | 9 | print(sys.argv[1].split(".")[-1]) 10 | -------------------------------------------------------------------------------- /bin/mtag/guestbook-read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | fetch latest msg from guestbook and return as tag 5 | 6 | example copyparty config to use this: 7 | --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook 8 | 9 | explained: 10 | for realpath srv/hello (served at /hello), write-only for eveyrone, 11 | enable file analysis on upload (e2ts), 12 | use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook", 13 | do this on all uploads regardless of extension, 14 | t10 = 10 seconds timeout for each dwonload, 15 | ad = parse file regardless if FFmpeg thinks it is audio or not 16 | p = request upload info as json on stdin (need ip) 17 | mte=+guestbook enabled indexing of that tag for this volume 18 | 19 | PS: this requires e2ts to be functional, 20 | meaning you need to do at least one of these: 21 | * apt install ffmpeg 22 | * pip3 install mutagen 23 | """ 24 | 25 | 26 | import json 27 | import os 28 | import sqlite3 29 | import sys 30 | 31 | 32 | # set 0 to allow infinite msgs from one IP, 33 | # other values delete older messages to make space, 34 | # so 1 only keeps latest msg 35 | NUM_MSGS_TO_KEEP = 1 36 | 37 | 38 | def main(): 39 | fp = os.path.abspath(sys.argv[1]) 40 | fdir = os.path.dirname(fp) 41 | 42 | zb = sys.stdin.buffer.read() 43 | zs = zb.decode("utf-8", "replace") 44 | md = json.loads(zs) 45 | 46 | ip = md["up_ip"] 47 | 48 | # can put the database inside `fdir` if you'd like, 49 | # by default it saves to PWD: 50 | # os.chdir(fdir) 51 | 52 | db = sqlite3.connect("guestbook.db3") 53 | with db: 54 | t = "select msg from gb where ip = ? order by ts desc" 55 | r = db.execute(t, (ip,)).fetchone() 56 | if r: 57 | print(r[0]) 58 | 59 | 60 | if __name__ == "__main__": 61 | main() 62 | -------------------------------------------------------------------------------- /bin/mtag/media-hash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import json 6 | import time 7 | import base64 8 | import hashlib 9 | import subprocess as sp 10 | 11 | try: 12 | from copyparty.util import fsenc 13 | except: 14 | 15 | def fsenc(p): 16 | return p.encode("utf-8") 17 | 18 | 19 | """ 20 | dep: ffmpeg 21 | """ 22 | 23 | 24 | def det(): 25 | # fmt: off 26 | cmd = [ 27 | b"ffmpeg", 28 | b"-nostdin", 29 | b"-hide_banner", 30 | b"-v", b"fatal", 31 | b"-i", fsenc(sys.argv[1]), 32 | b"-f", b"framemd5", 33 | b"-" 34 | ] 35 | # fmt: on 36 | 37 | p = sp.Popen(cmd, stdout=sp.PIPE) 38 | # ps = io.TextIOWrapper(p.stdout, encoding="utf-8") 39 | ps = p.stdout 40 | 41 | chans = {} 42 | for ln in ps: 43 | if ln.startswith(b"#stream#"): 44 | break 45 | 46 | m = re.match(r"^#media_type ([0-9]): ([a-zA-Z])", ln.decode("utf-8")) 47 | if m: 48 | chans[m.group(1)] = m.group(2) 49 | 50 | hashers = [hashlib.sha512(), hashlib.sha512()] 51 | for ln in ps: 52 | n = int(ln[:1]) 53 | v = ln.rsplit(b",", 1)[-1].strip() 54 | hashers[n].update(v) 55 | 56 | r = {} 57 | for k, v in chans.items(): 58 | dg = hashers[int(k)].digest()[:12] 59 | dg = base64.urlsafe_b64encode(dg).decode("ascii") 60 | r[v[0].lower() + "hash"] = dg 61 | 62 | print(json.dumps(r, indent=4)) 63 | 64 | 65 | def main(): 66 | try: 67 | det() 68 | except: 69 | pass # mute 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /bin/mtag/mousepad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess as sp 6 | 7 | 8 | """ 9 | mtp test -- opens a texteditor 10 | 11 | usage: 12 | -vsrv/v1:v1:r:c,mte=+x1:c,mtp=x1=ad,p,bin/mtag/mousepad.py 13 | 14 | explained: 15 | c,mte: list of tags to index in this volume 16 | c,mtp: add new tag provider 17 | x1: dummy tag to provide 18 | ad: dontcare if audio or not 19 | p: priority 1 (run after initial tag-scan with ffprobe or mutagen) 20 | """ 21 | 22 | 23 | def main(): 24 | env = os.environ.copy() 25 | env["DISPLAY"] = ":0.0" 26 | 27 | if False: 28 | # open the uploaded file 29 | fp = sys.argv[-1] 30 | else: 31 | # display stdin contents (`oth_tags`) 32 | fp = "/dev/stdin" 33 | 34 | p = sp.Popen(["/usr/bin/mousepad", fp]) 35 | p.communicate() 36 | 37 | 38 | main() 39 | -------------------------------------------------------------------------------- /bin/mtag/rclone-upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import os 5 | import subprocess as sp 6 | import sys 7 | import time 8 | 9 | try: 10 | from copyparty.util import fsenc 11 | except: 12 | 13 | def fsenc(p): 14 | return p.encode("utf-8") 15 | 16 | 17 | _ = r""" 18 | first checks the tag "vidchk" which must be "ok" to continue, 19 | then uploads all files to some cloud storage (RCLONE_REMOTE) 20 | and DELETES THE ORIGINAL FILES if rclone returns 0 ("success") 21 | 22 | deps: 23 | rclone 24 | 25 | usage: 26 | -mtp x2=t43200,ay,p2,bin/mtag/rclone-upload.py 27 | 28 | explained: 29 | t43200: timeout 12h 30 | ay: only process files which contain audio (including video with audio) 31 | p2: set priority 2 (after vidchk's suggested priority of 1), 32 | so the output of vidchk will be passed in here 33 | 34 | complete usage example as vflags along with vidchk: 35 | -vsrv/vidchk:vidchk:r:rw,ed:c,e2dsa,e2ts,mtp=vidchk=t600,p,bin/mtag/vidchk.py:c,mtp=rupload=t43200,ay,p2,bin/mtag/rclone-upload.py:c,mte=+vidchk,rupload 36 | 37 | setup: see https://rclone.org/drive/ 38 | 39 | if you wanna use this script standalone / separately from copyparty, 40 | either set CONDITIONAL_UPLOAD False or provide the following stdin: 41 | {"vidchk":"ok"} 42 | """ 43 | 44 | 45 | RCLONE_REMOTE = "notmybox" 46 | CONDITIONAL_UPLOAD = True 47 | 48 | 49 | def main(): 50 | fp = sys.argv[1] 51 | if CONDITIONAL_UPLOAD: 52 | zb = sys.stdin.buffer.read() 53 | zs = zb.decode("utf-8", "replace") 54 | md = json.loads(zs) 55 | 56 | chk = md.get("vidchk", None) 57 | if chk != "ok": 58 | print(f"vidchk={chk}", file=sys.stderr) 59 | sys.exit(1) 60 | 61 | dst = f"{RCLONE_REMOTE}:".encode("utf-8") 62 | cmd = [b"rclone", b"copy", b"--", fsenc(fp), dst] 63 | 64 | t0 = time.time() 65 | try: 66 | sp.check_call(cmd) 67 | except: 68 | print("rclone failed", file=sys.stderr) 69 | sys.exit(1) 70 | 71 | print(f"{time.time() - t0:.1f} sec") 72 | os.unlink(fsenc(fp)) 73 | 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /bin/mtag/res/twitter-unmute.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name twitter-unmute 3 | // @namespace http://ocv.me/ 4 | // @version 0.1 5 | // @description memes 6 | // @author ed 7 | // @match https://twitter.com/* 8 | // @icon https://www.google.com/s2/favicons?domain=twitter.com 9 | // @grant GM_addStyle 10 | // ==/UserScript== 11 | 12 | function grunnur() { 13 | setInterval(function () { 14 | //document.querySelector('div[aria-label="Unmute"]').click(); 15 | document.querySelector('video').muted = false; 16 | }, 200); 17 | } 18 | 19 | var scr = document.createElement('script'); 20 | scr.textContent = '(' + grunnur.toString() + ')();'; 21 | (document.head || document.getElementsByTagName('head')[0]).appendChild(scr); 22 | -------------------------------------------------------------------------------- /bin/mtag/res/yt-ipr.conf: -------------------------------------------------------------------------------- 1 | # example config file to use copyparty as a youtube manifest collector, 2 | # use with copyparty like: python copyparty.py -c yt-ipr.conf 3 | # 4 | # see docs/example.conf for a better explanation of the syntax, but 5 | # newlines are block separators, so adding blank lines inside a volume definition is bad 6 | # (use comments as separators instead) 7 | 8 | 9 | # create user ed, password wark 10 | u ed:wark 11 | 12 | 13 | # create a volume at /ytm which stores files at ./srv/ytm 14 | ./srv/ytm 15 | /ytm 16 | # write-only, but read-write for user ed 17 | w 18 | rw ed 19 | # rescan the volume on startup 20 | c e2dsa 21 | # collect tags from all new files since last scan 22 | c e2ts 23 | # optionally enable compression to make the files 50% smaller 24 | c pk 25 | # only allow uploads which are between 16k and 1m large 26 | c sz=16k-1m 27 | # allow up to 10 uploads over 5 minutes from each ip 28 | c maxn=10,300 29 | # move uploads into subfolders: YEAR-MONTH / DAY-HOUR / 30 | c rotf=%Y-%m/%d-%H 31 | # delete uploads when they are 24 hours old 32 | c lifetime=86400 33 | # add the parser and tell copyparty what tags it can expect from it 34 | c mtp=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires=bin/mtag/yt-ipr.py 35 | # decide which tags we want to index and in what order 36 | c mte=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires 37 | 38 | 39 | # create any other volumes you'd like down here, or merge this with an existing config file 40 | -------------------------------------------------------------------------------- /bin/mtag/res/yt-ipr.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name youtube-playerdata-hub 3 | // @match https://youtube.com/* 4 | // @match https://*.youtube.com/* 5 | // @version 1.0 6 | // @grant GM_addStyle 7 | // ==/UserScript== 8 | 9 | function main() { 10 | var server = 'https://127.0.0.1:3923/ytm?pw=wark', 11 | interval = 60; // sec 12 | 13 | var sent = {}; 14 | function send(txt, mf_url, desc) { 15 | if (sent[mf_url]) 16 | return; 17 | 18 | fetch(server + '&_=' + Date.now(), { method: "PUT", body: txt }); 19 | console.log('[yt-pdh] yeet %d bytes, %s', txt.length, desc); 20 | sent[mf_url] = 1; 21 | } 22 | 23 | function collect() { 24 | try { 25 | var pd = document.querySelector('ytd-watch-flexy'); 26 | if (!pd) 27 | return console.log('[yt-pdh] no video found'); 28 | 29 | pd = pd.playerData; 30 | var mu = pd.streamingData.dashManifestUrl || pd.streamingData.hlsManifestUrl; 31 | if (!mu || !mu.length) 32 | return console.log('[yt-pdh] no manifest found'); 33 | 34 | var desc = pd.videoDetails.videoId + ', ' + pd.videoDetails.title; 35 | send(JSON.stringify(pd), mu, desc); 36 | } 37 | catch (ex) { 38 | console.log("[yt-pdh]", ex); 39 | } 40 | } 41 | setInterval(collect, interval * 1000); 42 | } 43 | 44 | var scr = document.createElement('script'); 45 | scr.textContent = '(' + main.toString() + ')();'; 46 | (document.head || document.getElementsByTagName('head')[0]).appendChild(scr); 47 | console.log('[yt-pdh] a'); 48 | -------------------------------------------------------------------------------- /bin/mtag/sleep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import time 4 | import random 5 | 6 | v = random.random() * 6 7 | time.sleep(v) 8 | print(f"{v:.2f}") 9 | -------------------------------------------------------------------------------- /contrib/apache/copyparty.conf: -------------------------------------------------------------------------------- 1 | # if you would like to use unix-sockets (recommended), 2 | # you must run copyparty with one of the following: 3 | # 4 | # -i unix:777:/dev/shm/party.sock 5 | # -i unix:777:/dev/shm/party.sock,127.0.0.1 6 | # 7 | # if you are doing location-based proxying (such as `/stuff` below) 8 | # you must run copyparty with --rp-loc=stuff 9 | # 10 | # on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1 11 | 12 | 13 | LoadModule proxy_module modules/mod_proxy.so 14 | 15 | RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME} 16 | # NOTE: do not specify ProxyPassReverse 17 | 18 | 19 | ## 20 | ## then, enable one of the below: 21 | 22 | # use subdomain proxying to unix-socket (best) 23 | ProxyPass "/" "unix:///dev/shm/party.sock|http://whatever/" 24 | 25 | # use subdomain proxying to 127.0.0.1 (slower) 26 | #ProxyPass "/" "http://127.0.0.1:3923/" 27 | 28 | # use subpath proxying to 127.0.0.1 (slow and maybe buggy) 29 | #ProxyPass "/stuff" "http://127.0.0.1:3923/stuff" 30 | -------------------------------------------------------------------------------- /contrib/copyparty.bat: -------------------------------------------------------------------------------- 1 | exec python "$(dirname "$0")"/copyparty.py 2 | 3 | @rem on linux, the above will execute and the script will terminate 4 | @rem on windows, the rest of this script will run 5 | 6 | @echo off 7 | cls 8 | 9 | set py= 10 | for /f %%i in ('where python 2^>nul') do ( 11 | set "py=%%i" 12 | goto c1 13 | ) 14 | :c1 15 | 16 | if [%py%] == [] ( 17 | for /f %%i in ('where /r "%localappdata%\programs\python" python 2^>nul') do ( 18 | set "py=%%i" 19 | goto c2 20 | ) 21 | ) 22 | :c2 23 | 24 | if [%py%] == [] set "py=c:\python27\python.exe" 25 | 26 | if not exist "%py%" ( 27 | echo could not find python 28 | echo( 29 | pause 30 | exit /b 31 | ) 32 | 33 | start cmd /c %py% "%~dp0\copyparty.py" 34 | -------------------------------------------------------------------------------- /contrib/explorer-nothumbs-nofoldertypes.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | ; this will do 3 things, all optional: 4 | ; 1) disable thumbnails 5 | ; 2) delete all existing folder type settings/detections 6 | ; 3) disable folder type detection (force default columns) 7 | ; 8 | ; this makes the file explorer way faster, 9 | ; especially on slow/networked locations 10 | 11 | 12 | ; ===================================================================== 13 | ; 1) disable thumbnails 14 | 15 | [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced] 16 | "IconsOnly"=dword:00000001 17 | 18 | 19 | ; ===================================================================== 20 | ; 2) delete all existing folder type settings/detections 21 | 22 | [-HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags] 23 | 24 | [-HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU] 25 | 26 | 27 | ; ===================================================================== 28 | ; 3) disable folder type detection 29 | 30 | [HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell] 31 | "FolderType"="NotSpecified" 32 | -------------------------------------------------------------------------------- /contrib/flameshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # take a screenshot with flameshot and send it to copyparty; 5 | # the image url will be placed on your clipboard 6 | 7 | password=wark 8 | url=https://a.ocv.me/up/ 9 | filename=$(date +%Y-%m%d-%H%M%S).png 10 | 11 | flameshot gui -s -r | 12 | curl -T- $url$filename?pw=$password | 13 | tail -n 1 | 14 | xsel -ib 15 | -------------------------------------------------------------------------------- /contrib/haproxy/copyparty.conf: -------------------------------------------------------------------------------- 1 | # this config is essentially two separate examples; 2 | # 3 | # foo1 connects to copyparty using tcp, and 4 | # foo2 uses unix-sockets for 27% higher performance 5 | # 6 | # to use foo2 you must run copyparty with one of the following: 7 | # 8 | # -i unix:777:/dev/shm/party.sock 9 | # -i unix:777:/dev/shm/party.sock,127.0.0.1 10 | 11 | defaults 12 | mode http 13 | option forwardfor 14 | timeout connect 1s 15 | timeout client 610s 16 | timeout server 610s 17 | 18 | listen foo1 19 | bind *:8081 20 | server srv1 127.0.0.1:3923 maxconn 512 21 | 22 | listen foo2 23 | bind *:8082 24 | server srv1 /dev/shm/party.sock maxconn 512 25 | -------------------------------------------------------------------------------- /contrib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 💾🎉 redirect 7 | 8 | 23 | 24 | 25 | you probably want copyparty 26 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /contrib/ios/upload-to-copyparty.shortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/contrib/ios/upload-to-copyparty.shortcut -------------------------------------------------------------------------------- /contrib/ishare.iscu: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "copyparty", 3 | "RequestURL": "http://127.0.0.1:3923/screenshots/", 4 | "Headers": { 5 | "pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE", 6 | "accept": "json" 7 | }, 8 | "FileFormName": "f", 9 | "ResponseURL": "{{fileurl}}" 10 | } 11 | -------------------------------------------------------------------------------- /contrib/lighttpd/subdomain.conf: -------------------------------------------------------------------------------- 1 | # example usage for benchmarking: 2 | # 3 | # taskset -c 1 lighttpd -Df ~/dev/copyparty/contrib/lighttpd/subdomain.conf 4 | # 5 | # lighttpd can connect to copyparty using either tcp (127.0.0.1) 6 | # or a unix-socket, but unix-sockets are 37% faster because 7 | # lighttpd doesn't reuse tcp connections, so we're doing unix-sockets 8 | # 9 | # this means we must run copyparty with one of the following: 10 | # 11 | # -i unix:777:/dev/shm/party.sock 12 | # -i unix:777:/dev/shm/party.sock,127.0.0.1 13 | # 14 | # on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1 15 | 16 | server.port = 80 17 | server.document-root = "/var/empty" 18 | server.upload-dirs = ( "/dev/shm", "/tmp" ) 19 | server.modules = ( "mod_proxy" ) 20 | proxy.forwarded = ( "for" => 1, "proto" => 1 ) 21 | proxy.server = ( "" => ( ( "host" => "/dev/shm/party.sock" ) ) ) 22 | 23 | # if you really need to use tcp instead of unix-sockets, do this instead: 24 | #proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "3923" ) ) ) 25 | -------------------------------------------------------------------------------- /contrib/lighttpd/subpath.conf: -------------------------------------------------------------------------------- 1 | # example usage for benchmarking: 2 | # 3 | # taskset -c 1 lighttpd -Df ~/dev/copyparty/contrib/lighttpd/subpath.conf 4 | # 5 | # lighttpd can connect to copyparty using either tcp (127.0.0.1) 6 | # or a unix-socket, but unix-sockets are 37% faster because 7 | # lighttpd doesn't reuse tcp connections, so we're doing unix-sockets 8 | # 9 | # this means we must run copyparty with one of the following: 10 | # 11 | # -i unix:777:/dev/shm/party.sock 12 | # -i unix:777:/dev/shm/party.sock,127.0.0.1 13 | # 14 | # also since this example proxies a subpath instead of the 15 | # recommended subdomain-proxying, we must also specify this: 16 | # 17 | # --rp-loc files 18 | # 19 | # on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1 20 | 21 | server.port = 80 22 | server.document-root = "/var/empty" 23 | server.upload-dirs = ( "/dev/shm", "/tmp" ) 24 | server.modules = ( "mod_proxy" ) 25 | $HTTP["url"] =~ "^/files" { 26 | proxy.forwarded = ( "for" => 1, "proto" => 1 ) 27 | proxy.server = ( "" => ( ( "host" => "/dev/shm/party.sock" ) ) ) 28 | 29 | # if you really need to use tcp instead of unix-sockets, do this instead: 30 | #proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "3923" ) ) ) 31 | } 32 | -------------------------------------------------------------------------------- /contrib/openrc/copyparty: -------------------------------------------------------------------------------- 1 | #!/sbin/openrc-run 2 | 3 | # this will start `/usr/local/bin/copyparty-sfx.py` 4 | # and share '/mnt' with anonymous read+write 5 | # 6 | # installation: 7 | # cp -pv copyparty /etc/init.d && rc-update add copyparty 8 | # 9 | # you may want to: 10 | # change '/usr/bin/python' to another interpreter 11 | # change '/mnt::rw' to another location or permission-set 12 | 13 | name="$SVCNAME" 14 | command_background=true 15 | pidfile="/var/run/$SVCNAME.pid" 16 | 17 | command="/usr/bin/python3 /usr/local/bin/copyparty-sfx.py" 18 | command_args="-q -v /mnt::rw" 19 | -------------------------------------------------------------------------------- /contrib/package/arch/copyparty.conf: -------------------------------------------------------------------------------- 1 | ## import all *.conf files from the current folder (/etc/copyparty.d) 2 | % ./ 3 | 4 | # add additional .conf files to this folder; 5 | # see example config files for reference: 6 | # https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf 7 | # https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d 8 | -------------------------------------------------------------------------------- /contrib/package/arch/copyparty.service: -------------------------------------------------------------------------------- 1 | # this will start `/usr/bin/copyparty-sfx.py` 2 | # and read config from `/etc/copyparty.d/*.conf` 3 | # 4 | # you probably want to: 5 | # change "User=cpp" and "/home/cpp/" to another user 6 | # 7 | # unless you add -q to disable logging, you may want to remove the 8 | # following line to allow buffering (slightly better performance): 9 | # Environment=PYTHONUNBUFFERED=x 10 | 11 | [Unit] 12 | Description=copyparty file server 13 | 14 | [Service] 15 | Type=notify 16 | SyslogIdentifier=copyparty 17 | Environment=PYTHONUNBUFFERED=x 18 | WorkingDirectory=/var/lib/copyparty-jail 19 | ExecReload=/bin/kill -s USR1 $MAINPID 20 | 21 | # user to run as + where the TLS certificate is (if any) 22 | User=cpp 23 | Environment=XDG_CONFIG_HOME=/home/cpp/.config 24 | 25 | # stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running 26 | ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' 27 | 28 | # run copyparty 29 | ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init 30 | 31 | [Install] 32 | WantedBy=multi-user.target 33 | -------------------------------------------------------------------------------- /contrib/package/arch/index.md: -------------------------------------------------------------------------------- 1 | this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured 2 | 3 | please add some `*.conf` files to `/etc/copyparty.d/` 4 | -------------------------------------------------------------------------------- /contrib/package/arch/prisonparty.service: -------------------------------------------------------------------------------- 1 | # this will start `/usr/bin/copyparty-sfx.py` 2 | # in a chroot, preventing accidental access elsewhere, 3 | # and read copyparty config from `/etc/copyparty.d/*.conf` 4 | # 5 | # expose additional filesystem locations to copyparty 6 | # by listing them between the last `cpp` and `--` 7 | # 8 | # `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000) 9 | # 10 | # unless you add -q to disable logging, you may want to remove the 11 | # following line to allow buffering (slightly better performance): 12 | # Environment=PYTHONUNBUFFERED=x 13 | 14 | [Unit] 15 | Description=copyparty file server 16 | 17 | [Service] 18 | SyslogIdentifier=prisonparty 19 | Environment=PYTHONUNBUFFERED=x 20 | WorkingDirectory=/var/lib/copyparty-jail 21 | ExecReload=/bin/kill -s USR1 $MAINPID 22 | 23 | # stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running 24 | ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' 25 | 26 | # run copyparty 27 | ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \ 28 | /etc/copyparty.d \ 29 | -- \ 30 | /usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init 31 | 32 | [Install] 33 | WantedBy=multi-user.target 34 | -------------------------------------------------------------------------------- /contrib/package/nix/copyparty/pin.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/9001/copyparty/releases/download/v1.17.2/copyparty-sfx.py", 3 | "version": "1.17.2", 4 | "hash": "sha256-qY3QTLthJLpvO9yo2kiwM2cT3eqg5/cGvGzp+JHy4Ko=" 5 | } -------------------------------------------------------------------------------- /contrib/plugins/README.md: -------------------------------------------------------------------------------- 1 | # example resource files 2 | 3 | can be provided to copyparty to tweak things 4 | 5 | 6 | 7 | ## example `.epilogue.html` 8 | save one of these as `.epilogue.html` inside a folder to customize it: 9 | 10 | * [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png) 11 | 12 | 13 | 14 | ## example browser-js 15 | point `--js-browser` to one of these by URL: 16 | 17 | * [`minimal-up2k.js`](minimal-up2k.js) is similar to the above `minimal-up2k.html` except it applies globally to all write-only folders 18 | * [`quickmove.js`](quickmove.js) adds a hotkey to move selected files into a subfolder 19 | * [`up2k-hooks.js`](up2k-hooks.js) lets you specify a ruleset for files to skip uploading 20 | * [`up2k-hook-ytid.js`](up2k-hook-ytid.js) is a more specific example checking youtube-IDs against some API 21 | 22 | 23 | 24 | ## example any-js 25 | point `--js-browser` and/or `--js-other` to one of these by URL: 26 | 27 | * [`banner.js`](banner.js) shows a very enterprise [legal-banner](https://github.com/user-attachments/assets/8ae8e087-b209-449c-b08d-74e040f0284b) 28 | 29 | 30 | 31 | ## example browser-css 32 | point `--css-browser` to one of these by URL: 33 | 34 | * [`browser-icons.css`](browser-icons.css) adds filetype icons 35 | 36 | 37 | 38 | ## meadup.js 39 | 40 | * turns copyparty into chromecast just more flexible (and probably way more buggy) 41 | * usage: put the js somewhere in the webroot and `--js-browser /memes/meadup.js` 42 | -------------------------------------------------------------------------------- /contrib/plugins/browser-icons.css: -------------------------------------------------------------------------------- 1 | /* video, alternative 1: 2 | top-left icon, just like the other formats 3 | ======================================================================= 4 | 5 | #ggrid>a:is( 6 | [href$=".mkv"i], 7 | [href$=".mp4"i], 8 | [href$=".webm"i], 9 | ):before { 10 | content: '📺'; 11 | } 12 | */ 13 | 14 | 15 | 16 | /* video, alternative 2: 17 | play-icon in the middle of the thumbnail 18 | ======================================================================= 19 | */ 20 | #ggrid>a:is( 21 | [href$=".mkv"i], 22 | [href$=".mp4"i], 23 | [href$=".webm"i], 24 | ) { 25 | position: relative; 26 | overflow: hidden; 27 | } 28 | #ggrid>a:is( 29 | [href$=".mkv"i], 30 | [href$=".mp4"i], 31 | [href$=".webm"i], 32 | ):before { 33 | content: '▶'; 34 | opacity: .8; 35 | margin: 0; 36 | padding: 1em .5em 1em .7em; 37 | border-radius: 9em; 38 | line-height: 0; 39 | color: #fff; 40 | text-shadow: none; 41 | background: rgba(0, 0, 0, 0.7); 42 | left: calc(50% - 1em); 43 | top: calc(50% - 1.4em); 44 | } 45 | 46 | 47 | 48 | /* audio */ 49 | #ggrid>a:is( 50 | [href$=".mp3"i], 51 | [href$=".ogg"i], 52 | [href$=".opus"i], 53 | [href$=".flac"i], 54 | [href$=".m4a"i], 55 | [href$=".aac"i], 56 | ):before { 57 | content: '🎵'; 58 | } 59 | 60 | 61 | 62 | /* image */ 63 | #ggrid>a:is( 64 | [href$=".jpg"i], 65 | [href$=".jpeg"i], 66 | [href$=".png"i], 67 | [href$=".gif"i], 68 | [href$=".webp"i], 69 | ):before { 70 | content: '🎨'; 71 | } 72 | -------------------------------------------------------------------------------- /contrib/plugins/minimal-up2k.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 45 | 46 | show advanced options 47 | -------------------------------------------------------------------------------- /contrib/plugins/minimal-up2k.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | makes the up2k ui REALLY minimal by hiding a bunch of stuff 4 | 5 | almost the same as minimal-up2k.html except this one...: 6 | 7 | -- applies to every write-only folder when used with --js-browser 8 | 9 | -- only applies if javascript is enabled 10 | 11 | -- doesn't hide the total upload ETA display 12 | 13 | -- looks slightly better 14 | 15 | 16 | ======================== 17 | == USAGE INSTRUCTIONS == 18 | 19 | 1. create a volume which anyone can read from (if you haven't already) 20 | 2. copy this file into that volume, so anyone can download it 21 | 3. enable the plugin by telling the webbrowser to load this file; 22 | assuming the URL to the public volume is /res/, and 23 | assuming you're using config-files, then add this to your config: 24 | 25 | [global] 26 | js-browser: /res/minimal-up2k.js 27 | 28 | alternatively, if you're not using config-files, then 29 | add the following commandline argument instead: 30 | --js-browser=/res/minimal-up2k.js 31 | 32 | */ 33 | 34 | var u2min = ` 35 | 68 | 69 | show advanced options 70 | `; 71 | 72 | if (!has(perms, 'read')) { 73 | var e2 = mknod('div'); 74 | e2.innerHTML = u2min; 75 | ebi('wrap').insertBefore(e2, QS('#wfp')); 76 | } 77 | -------------------------------------------------------------------------------- /contrib/plugins/up2k-hooks.js: -------------------------------------------------------------------------------- 1 | // hooks into up2k 2 | 3 | function up2k_namefilter(good_files, nil_files, bad_files, hooks) { 4 | // is called when stuff is dropped into the browser, 5 | // after iterating through the directory tree and discovering all files, 6 | // before the upload confirmation dialogue is shown 7 | 8 | // good_files will successfully upload 9 | // nil_files are empty files and will show an alert in the final hook 10 | // bad_files are unreadable and cannot be uploaded 11 | var file_lists = [good_files, nil_files, bad_files]; 12 | 13 | // build a list of filenames 14 | var filenames = []; 15 | for (var lst of file_lists) 16 | for (var ent of lst) 17 | filenames.push(ent[1]); 18 | 19 | toast.inf(5, "running database query..."); 20 | 21 | // simulate delay while passing the list to some api for checking 22 | setTimeout(function () { 23 | 24 | // only keep webm files as an example 25 | var new_lists = []; 26 | for (var lst of file_lists) { 27 | var keep = []; 28 | new_lists.push(keep); 29 | 30 | for (var ent of lst) 31 | if (/\.webm$/.test(ent[1])) 32 | keep.push(ent); 33 | } 34 | 35 | // finally, call the next hook in the chain 36 | [good_files, nil_files, bad_files] = new_lists; 37 | hooks[0](good_files, nil_files, bad_files, hooks.slice(1)); 38 | 39 | }, 1000); 40 | } 41 | 42 | // register 43 | up2k_hooks.push(function () { 44 | up2k.gotallfiles.unshift(up2k_namefilter); 45 | }); 46 | -------------------------------------------------------------------------------- /contrib/rc/copyparty: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # PROVIDE: copyparty 4 | # REQUIRE: networking 5 | # KEYWORD: 6 | 7 | . /etc/rc.subr 8 | 9 | name="copyparty" 10 | rcvar="copyparty_enable" 11 | copyparty_user="copyparty" 12 | copyparty_args="-e2dsa -v /storage:/storage:r" # change as you see fit 13 | copyparty_command="/usr/local/bin/python3.9 /usr/local/copyparty/copyparty-sfx.py ${copyparty_args}" 14 | pidfile="/var/run/copyparty/${name}.pid" 15 | command="/usr/sbin/daemon" 16 | command_args="-P ${pidfile} -r -f ${copyparty_command}" 17 | 18 | stop_postcmd="copyparty_shutdown" 19 | 20 | copyparty_shutdown() 21 | { 22 | if [ -e "${pidfile}" ]; then 23 | echo "Stopping supervising daemon." 24 | kill -s TERM `cat ${pidfile}` 25 | fi 26 | } 27 | 28 | load_rc_config $name 29 | : ${copyparty_enable:=no} 30 | 31 | run_rc_command "$1" 32 | -------------------------------------------------------------------------------- /contrib/send-to-cpp.contextlet.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "// https://addons.mozilla.org/en-US/firefox/addon/contextlets/\n// https://github.com/davidmhammond/contextlets\n\nvar url = 'http://partybox.local:3923/';\nvar pw = 'wark';\n\nvar xhr = new XMLHttpRequest();\nxhr.msg = this.info.linkUrl || this.info.srcUrl;\nxhr.open('POST', url, true);\nxhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');\nxhr.setRequestHeader('PW', pw);\nxhr.send('msg=' + xhr.msg);\n", 3 | "contexts": [ 4 | "link" 5 | ], 6 | "icons": null, 7 | "patterns": "", 8 | "scope": "background", 9 | "title": "send to cpp", 10 | "type": "normal" 11 | } -------------------------------------------------------------------------------- /contrib/sharex.sxcu: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "15.0.0", 3 | "Name": "copyparty", 4 | "DestinationType": "ImageUploader", 5 | "RequestMethod": "POST", 6 | "RequestURL": "http://127.0.0.1:3923/sharex", 7 | "Parameters": { 8 | "j": null 9 | }, 10 | "Headers": { 11 | "pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE" 12 | }, 13 | "Body": "MultipartFormData", 14 | "Arguments": { 15 | "act": "bput" 16 | }, 17 | "FileFormName": "f", 18 | "URL": "{json:files[0].url}" 19 | } 20 | -------------------------------------------------------------------------------- /contrib/sharex12.sxcu: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "copyparty", 3 | "DestinationType": "ImageUploader, TextUploader, FileUploader", 4 | "RequestURL": "http://127.0.0.1:3923/sharex", 5 | "FileFormName": "f", 6 | "Arguments": { 7 | "act": "bput" 8 | }, 9 | "Headers": { 10 | "accept": "url", 11 | "pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contrib/systemd/cfssl.service: -------------------------------------------------------------------------------- 1 | # NOTE: this is now a built-in feature in copyparty 2 | # but you may still want this if you have specific needs 3 | # 4 | # systemd service which generates a new TLS certificate on each boot, 5 | # that way the one-year expiry time won't cause any issues -- 6 | # just have everyone trust the ca.pem once every 10 years 7 | # 8 | # assumptions/placeholder values: 9 | # * this script and copyparty runs as user "cpp" 10 | # * copyparty repo is at ~cpp/dev/copyparty 11 | # * CA is named partylan 12 | # * server IPs = 10.1.2.3 and 192.168.123.1 13 | # * server hostname = party.lan 14 | 15 | [Unit] 16 | Description=copyparty certificate generator 17 | Before=copyparty.service 18 | 19 | [Service] 20 | User=cpp 21 | Type=oneshot 22 | SyslogIdentifier=cpp-cert 23 | ExecStart=/bin/bash -c 'cd ~/dev/copyparty/contrib && ./cfssl.sh partylan 10.1.2.3,192.168.123.1,party.lan y' 24 | 25 | [Install] 26 | WantedBy=multi-user.target 27 | -------------------------------------------------------------------------------- /contrib/systemd/copyparty.conf: -------------------------------------------------------------------------------- 1 | # not actually YAML but lets pretend: 2 | # -*- mode: yaml -*- 3 | # vim: ft=yaml: 4 | 5 | 6 | # put this file in /etc/ 7 | 8 | 9 | [global] 10 | e2dsa # enable file indexing and filesystem scanning 11 | e2ts # and enable multimedia indexing 12 | ansi # and colors in log messages 13 | 14 | # disable logging to stdout/journalctl and log to a file instead; 15 | # $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd) 16 | # and copyparty replaces %Y-%m%d with Year-MonthDay, so the 17 | # full path will be something like /var/log/copyparty/2023-1130.txt 18 | # (note: enable compression by adding .xz at the end) 19 | q, lo: $LOGS_DIRECTORY/%Y-%m%d.log 20 | 21 | # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) 22 | # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) 23 | # ftp: 3921 # enable ftp server on port 3921 24 | # p: 3939 # listen on another port 25 | # df: 16 # stop accepting uploads if less than 16 GB free disk space 26 | # ver # show copyparty version in the controlpanel 27 | # grid # show thumbnails/grid-view by default 28 | # theme: 2 # monokai 29 | # name: datasaver # change the server-name that's displayed in the browser 30 | # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) 31 | # no-robots, force-js # make it harder for search engines to read your server 32 | 33 | 34 | [accounts] 35 | ed: wark # username: password 36 | 37 | 38 | [/] # create a volume at "/" (the webroot), which will 39 | /mnt # share the contents of the "/mnt" folder 40 | accs: 41 | rw: * # everyone gets read-write access, but 42 | rwmda: ed # the user "ed" gets read-write-move-delete-admin 43 | -------------------------------------------------------------------------------- /contrib/systemd/prisonparty.service: -------------------------------------------------------------------------------- 1 | # this will start `/usr/local/bin/copyparty-sfx.py` 2 | # in a chroot, preventing accidental access elsewhere, 3 | # and share '/mnt' with anonymous read+write 4 | # 5 | # installation: 6 | # 1) put copyparty-sfx.py and prisonparty.sh in /usr/local/bin 7 | # 2) cp -pv prisonparty.service /etc/systemd/system && systemctl enable --now prisonparty 8 | # 9 | # expose additional filesystem locations to copyparty 10 | # by listing them between the last `cpp` and `--` 11 | # 12 | # `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000) 13 | # 14 | # you may want to: 15 | # change '/mnt::rw' to another location or permission-set 16 | # (remember to change the '/mnt' chroot arg too) 17 | # 18 | # unless you add -q to disable logging, you may want to remove the 19 | # following line to allow buffering (slightly better performance): 20 | # Environment=PYTHONUNBUFFERED=x 21 | 22 | [Unit] 23 | Description=copyparty file server 24 | 25 | [Service] 26 | SyslogIdentifier=prisonparty 27 | Environment=PYTHONUNBUFFERED=x 28 | WorkingDirectory=/var/lib/copyparty-jail 29 | ExecReload=/bin/kill -s USR1 $MAINPID 30 | 31 | # stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running 32 | ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' 33 | 34 | # run copyparty 35 | ExecStart=/bin/bash /usr/local/bin/prisonparty.sh /var/lib/copyparty-jail cpp cpp \ 36 | /mnt \ 37 | -- \ 38 | /usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::rw 39 | 40 | [Install] 41 | WantedBy=multi-user.target 42 | -------------------------------------------------------------------------------- /contrib/themes/bsod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/contrib/themes/bsod.png -------------------------------------------------------------------------------- /contrib/traefik/copyparty.yaml: -------------------------------------------------------------------------------- 1 | # ./traefik --configFile=copyparty.yaml 2 | 3 | entryPoints: 4 | web: 5 | address: :8080 6 | transport: 7 | # don't disconnect during big uploads 8 | respondingTimeouts: 9 | readTimeout: "0s" 10 | log: 11 | level: DEBUG 12 | providers: 13 | file: 14 | # WARNING: must be same filename as current file 15 | filename: "copyparty.yaml" 16 | http: 17 | services: 18 | service-cpp: 19 | loadBalancer: 20 | servers: 21 | - url: "http://127.0.0.1:3923/" 22 | routers: 23 | my-router: 24 | rule: "PathPrefix(`/`)" 25 | service: service-cpp 26 | -------------------------------------------------------------------------------- /contrib/webdav-cfg.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem removes the 47.6 MiB filesize limit when downloading from webdav 3 | rem + optionally allows/enables password-auth over plaintext http 4 | rem + optionally helps disable wpad, removing the 10sec latency 5 | 6 | net session >nul 2>&1 7 | if %errorlevel% neq 0 ( 8 | echo sorry, you must run this as administrator 9 | pause 10 | exit /b 11 | ) 12 | 13 | reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters /v FileSizeLimitInBytes /t REG_DWORD /d 0xffffffff /f 14 | reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters /v FsCtlRequestTimeoutInSec /t REG_DWORD /d 0xffffffff /f 15 | 16 | echo( 17 | echo OK; 18 | echo allow webdav basic-auth over plaintext http? 19 | echo Y: login works, but the password will be visible in wireshark etc 20 | echo N: login will NOT work unless you use https and valid certificates 21 | choice 22 | if %errorlevel% equ 1 ( 23 | reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters /v BasicAuthLevel /t REG_DWORD /d 0x2 /f 24 | rem default is 1 (require tls) 25 | ) 26 | 27 | echo( 28 | echo OK; 29 | echo do you want to disable wpad? 30 | echo can give a HUGE speed boost depending on network settings 31 | choice 32 | if %errorlevel% equ 1 ( 33 | echo( 34 | echo i'm about to open the [Connections] tab in [Internet Properties] for you; 35 | echo please click [LAN settings] and disable [Automatically detect settings] 36 | echo( 37 | pause 38 | control inetcpl.cpl,,4 39 | ) 40 | 41 | net stop webclient 42 | net start webclient 43 | echo( 44 | echo OK; all done 45 | pause 46 | -------------------------------------------------------------------------------- /contrib/windows/copyparty-ctmp.bat: -------------------------------------------------------------------------------- 1 | rem run copyparty.exe on machines with busted environment variables 2 | cmd /v /c "set TMP=\tmp && copyparty.exe" 3 | -------------------------------------------------------------------------------- /copyparty/__version__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | VERSION = (1, 17, 2) 4 | CODENAME = "mixtape.m3u" 5 | BUILD_DT = (2025, 5, 27) 6 | 7 | S_VERSION = ".".join(map(str, VERSION)) 8 | S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) 9 | 10 | __version__ = S_VERSION 11 | __build_dt__ = S_BUILD_DT 12 | 13 | # I'm all ears 14 | -------------------------------------------------------------------------------- /copyparty/bos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/bos/__init__.py -------------------------------------------------------------------------------- /copyparty/bos/bos.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | import os 5 | 6 | from ..util import SYMTIME, fsdec, fsenc 7 | from . import path as path 8 | 9 | if True: # pylint: disable=using-constant-test 10 | from typing import Any, Optional 11 | 12 | _ = (path,) 13 | __all__ = ["path"] 14 | 15 | # grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c 16 | # printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')" 17 | 18 | 19 | def chmod(p: str, mode: int) -> None: 20 | return os.chmod(fsenc(p), mode) 21 | 22 | 23 | def listdir(p: str = ".") -> list[str]: 24 | return [fsdec(x) for x in os.listdir(fsenc(p))] 25 | 26 | 27 | def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> bool: 28 | bname = fsenc(name) 29 | try: 30 | os.makedirs(bname, mode) 31 | return True 32 | except: 33 | if not exist_ok or not os.path.isdir(bname): 34 | raise 35 | return False 36 | 37 | 38 | def mkdir(p: str, mode: int = 0o755) -> None: 39 | return os.mkdir(fsenc(p), mode) 40 | 41 | 42 | def open(p: str, *a, **ka) -> int: 43 | return os.open(fsenc(p), *a, **ka) 44 | 45 | 46 | def readlink(p: str) -> str: 47 | return fsdec(os.readlink(fsenc(p))) 48 | 49 | 50 | def rename(src: str, dst: str) -> None: 51 | return os.rename(fsenc(src), fsenc(dst)) 52 | 53 | 54 | def replace(src: str, dst: str) -> None: 55 | return os.replace(fsenc(src), fsenc(dst)) 56 | 57 | 58 | def rmdir(p: str) -> None: 59 | return os.rmdir(fsenc(p)) 60 | 61 | 62 | def stat(p: str) -> os.stat_result: 63 | return os.stat(fsenc(p)) 64 | 65 | 66 | def unlink(p: str) -> None: 67 | return os.unlink(fsenc(p)) 68 | 69 | 70 | def utime( 71 | p: str, times: Optional[tuple[float, float]] = None, follow_symlinks: bool = True 72 | ) -> None: 73 | if SYMTIME: 74 | return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks) 75 | else: 76 | return os.utime(fsenc(p), times) 77 | 78 | 79 | if hasattr(os, "lstat"): 80 | 81 | def lstat(p: str) -> os.stat_result: 82 | return os.lstat(fsenc(p)) 83 | 84 | else: 85 | lstat = stat 86 | -------------------------------------------------------------------------------- /copyparty/bos/path.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | import os 5 | 6 | from ..util import SYMTIME, fsdec, fsenc 7 | 8 | 9 | def abspath(p: str) -> str: 10 | return fsdec(os.path.abspath(fsenc(p))) 11 | 12 | 13 | def exists(p: str) -> bool: 14 | return os.path.exists(fsenc(p)) 15 | 16 | 17 | def getmtime(p: str, follow_symlinks: bool = True) -> float: 18 | if not follow_symlinks and SYMTIME: 19 | return os.lstat(fsenc(p)).st_mtime 20 | else: 21 | return os.path.getmtime(fsenc(p)) 22 | 23 | 24 | def getsize(p: str) -> int: 25 | return os.path.getsize(fsenc(p)) 26 | 27 | 28 | def isfile(p: str) -> bool: 29 | return os.path.isfile(fsenc(p)) 30 | 31 | 32 | def isdir(p: str) -> bool: 33 | return os.path.isdir(fsenc(p)) 34 | 35 | 36 | def islink(p: str) -> bool: 37 | return os.path.islink(fsenc(p)) 38 | 39 | 40 | def lexists(p: str) -> bool: 41 | return os.path.lexists(fsenc(p)) 42 | 43 | 44 | def realpath(p: str) -> str: 45 | return fsdec(os.path.realpath(fsenc(p))) 46 | -------------------------------------------------------------------------------- /copyparty/broker_thr.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | import os 5 | import threading 6 | 7 | from .__init__ import TYPE_CHECKING 8 | from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue 9 | from .httpsrv import HttpSrv 10 | from .util import HMaccas 11 | 12 | if TYPE_CHECKING: 13 | from .svchub import SvcHub 14 | 15 | if True: # pylint: disable=using-constant-test 16 | from typing import Any, Union 17 | 18 | 19 | class BrokerThr(BrokerCli): 20 | """external api; behaves like BrokerMP but using plain threads""" 21 | 22 | def __init__(self, hub: "SvcHub") -> None: 23 | super(BrokerThr, self).__init__() 24 | 25 | self.hub = hub 26 | self.log = hub.log 27 | self.args = hub.args 28 | self.asrv = hub.asrv 29 | 30 | self.mutex = threading.Lock() 31 | self.num_workers = 1 32 | 33 | # instantiate all services here (TODO: inheritance?) 34 | self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8) 35 | self.httpsrv = HttpSrv(self, None) 36 | self.reload = self.noop 37 | self.reload_sessions = self.noop 38 | 39 | def shutdown(self) -> None: 40 | # self.log("broker", "shutting down") 41 | self.httpsrv.shutdown() 42 | 43 | def noop(self) -> None: 44 | pass 45 | 46 | def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]: 47 | 48 | # new ipc invoking managed service in hub 49 | obj = self.hub 50 | for node in dest.split("."): 51 | obj = getattr(obj, node) 52 | 53 | return NotExQueue(obj(*args)) # type: ignore 54 | 55 | def say(self, dest: str, *args: Any) -> None: 56 | if dest == "httpsrv.listen": 57 | self.httpsrv.listen(args[0], 1) 58 | return 59 | 60 | if dest == "httpsrv.set_netdevs": 61 | self.httpsrv.set_netdevs(args[0]) 62 | return 63 | 64 | # new ipc invoking managed service in hub 65 | obj = self.hub 66 | for node in dest.split("."): 67 | obj = getattr(obj, node) 68 | 69 | obj(*args) # type: ignore 70 | -------------------------------------------------------------------------------- /copyparty/broker_util.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | import argparse 5 | import traceback 6 | 7 | from queue import Queue 8 | 9 | from .__init__ import TYPE_CHECKING 10 | from .authsrv import AuthSrv 11 | from .util import HMaccas, Pebkac 12 | 13 | if True: # pylint: disable=using-constant-test 14 | from typing import Any, Optional, Union 15 | 16 | from .util import RootLogger 17 | 18 | if TYPE_CHECKING: 19 | from .httpsrv import HttpSrv 20 | 21 | 22 | class ExceptionalQueue(Queue, object): 23 | def get(self, block: bool = True, timeout: Optional[float] = None) -> Any: 24 | rv = super(ExceptionalQueue, self).get(block, timeout) 25 | 26 | if isinstance(rv, list): 27 | if rv[0] == "exception": 28 | if rv[1] == "pebkac": 29 | raise Pebkac(*rv[2:]) 30 | else: 31 | raise rv[2] 32 | 33 | return rv 34 | 35 | 36 | class NotExQueue(object): 37 | """ 38 | BrokerThr uses this instead of ExceptionalQueue; 7x faster 39 | """ 40 | 41 | def __init__(self, rv: Any) -> None: 42 | self.rv = rv 43 | 44 | def get(self) -> Any: 45 | return self.rv 46 | 47 | 48 | class BrokerCli(object): 49 | """ 50 | helps mypy understand httpsrv.broker but still fails a few levels deeper, 51 | for example resolving httpconn.* in httpcli -- see lines tagged #mypy404 52 | """ 53 | 54 | log: "RootLogger" 55 | args: argparse.Namespace 56 | asrv: AuthSrv 57 | httpsrv: "HttpSrv" 58 | iphash: HMaccas 59 | 60 | def __init__(self) -> None: 61 | pass 62 | 63 | def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]: 64 | return ExceptionalQueue(1) 65 | 66 | def say(self, dest: str, *args: Any) -> None: 67 | pass 68 | 69 | 70 | def try_exec(want_retval: Union[bool, int], func: Any, *args: list[Any]) -> Any: 71 | try: 72 | return func(*args) 73 | 74 | except Pebkac as ex: 75 | if not want_retval: 76 | raise 77 | 78 | return ["exception", "pebkac", ex.code, str(ex)] 79 | 80 | except Exception as ex: 81 | if not want_retval: 82 | raise 83 | 84 | return ["exception", "stack", ex] 85 | -------------------------------------------------------------------------------- /copyparty/res/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/res/__init__.py -------------------------------------------------------------------------------- /copyparty/stolen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/stolen/__init__.py -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/README.md: -------------------------------------------------------------------------------- 1 | `dnslib` but heavily simplified/feature-stripped 2 | 3 | L: MIT 4 | Copyright (c) 2010 - 2017 Paul Chakravarti 5 | https://github.com/paulc/dnslib/ 6 | -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | L: MIT 5 | Copyright (c) 2010 - 2017 Paul Chakravarti 6 | https://github.com/paulc/dnslib/tree/0.9.23 7 | """ 8 | 9 | from .dns import * 10 | 11 | version = "0.9.23" 12 | -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/bimap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import types 4 | 5 | 6 | class BimapError(Exception): 7 | pass 8 | 9 | 10 | class Bimap(object): 11 | def __init__(self, name, forward, error=AttributeError): 12 | self.name = name 13 | self.error = error 14 | self.forward = forward.copy() 15 | self.reverse = dict([(v, k) for (k, v) in list(forward.items())]) 16 | 17 | def get(self, k, default=None): 18 | try: 19 | return self.forward[k] 20 | except KeyError: 21 | return default or str(k) 22 | 23 | def __getitem__(self, k): 24 | try: 25 | return self.forward[k] 26 | except KeyError: 27 | if isinstance(self.error, types.FunctionType): 28 | return self.error(self.name, k, True) 29 | else: 30 | raise self.error("%s: Invalid forward lookup: [%s]" % (self.name, k)) 31 | 32 | def __getattr__(self, k): 33 | try: 34 | if k == "__wrapped__": 35 | raise AttributeError() 36 | return self.reverse[k] 37 | except KeyError: 38 | if isinstance(self.error, types.FunctionType): 39 | return self.error(self.name, k, False) 40 | else: 41 | raise self.error("%s: Invalid reverse lookup: [%s]" % (self.name, k)) 42 | -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/bit.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import print_function 4 | 5 | 6 | def get_bits(data, offset, bits=1): 7 | mask = ((1 << bits) - 1) << offset 8 | return (data & mask) >> offset 9 | 10 | 11 | def set_bits(data, value, offset, bits=1): 12 | mask = ((1 << bits) - 1) << offset 13 | clear = 0xFFFF ^ mask 14 | data = (data & clear) | ((value << offset) & mask) 15 | return data 16 | -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/buffer.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import binascii 4 | import struct 5 | 6 | 7 | class BufferError(Exception): 8 | pass 9 | 10 | 11 | class Buffer(object): 12 | def __init__(self, data=b""): 13 | self.data = bytearray(data) 14 | self.offset = 0 15 | 16 | def remaining(self): 17 | return len(self.data) - self.offset 18 | 19 | def get(self, length): 20 | if length > self.remaining(): 21 | raise BufferError( 22 | "Not enough bytes [offset=%d,remaining=%d,requested=%d]" 23 | % (self.offset, self.remaining(), length) 24 | ) 25 | start = self.offset 26 | end = self.offset + length 27 | self.offset += length 28 | return bytes(self.data[start:end]) 29 | 30 | def hex(self): 31 | return binascii.hexlify(self.data) 32 | 33 | def pack(self, fmt, *args): 34 | self.offset += struct.calcsize(fmt) 35 | self.data += struct.pack(fmt, *args) 36 | 37 | def append(self, s): 38 | self.offset += len(s) 39 | self.data += s 40 | 41 | def update(self, ptr, fmt, *args): 42 | s = struct.pack(fmt, *args) 43 | self.data[ptr : ptr + len(s)] = s 44 | 45 | def unpack(self, fmt): 46 | try: 47 | data = self.get(struct.calcsize(fmt)) 48 | return struct.unpack(fmt, data) 49 | except struct.error: 50 | raise BufferError( 51 | "Error unpacking struct '%s' <%s>" 52 | % (fmt, binascii.hexlify(data).decode()) 53 | ) 54 | 55 | def __len__(self): 56 | return len(self.data) 57 | -------------------------------------------------------------------------------- /copyparty/stolen/dnslib/ranges.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys 4 | 5 | if sys.version_info < (3,): 6 | int_types = ( 7 | int, 8 | long, 9 | ) 10 | byte_types = (str, bytearray) 11 | else: 12 | int_types = (int,) 13 | byte_types = (bytes, bytearray) 14 | 15 | 16 | def check_instance(name, val, types): 17 | if not isinstance(val, types): 18 | raise ValueError( 19 | "Attribute '%s' must be instance of %s [%s]" % (name, types, type(val)) 20 | ) 21 | 22 | 23 | def check_bytes(name, val): 24 | return check_instance(name, val, byte_types) 25 | 26 | 27 | def range_property(attr, min, max): 28 | def getter(obj): 29 | return getattr(obj, "_%s" % attr) 30 | 31 | def setter(obj, val): 32 | if isinstance(val, int_types) and min <= val <= max: 33 | setattr(obj, "_%s" % attr, val) 34 | else: 35 | raise ValueError( 36 | "Attribute '%s' must be between %d-%d [%s]" % (attr, min, max, val) 37 | ) 38 | 39 | return property(getter, setter) 40 | 41 | 42 | def B(attr): 43 | return range_property(attr, 0, 255) 44 | 45 | 46 | def H(attr): 47 | return range_property(attr, 0, 65535) 48 | 49 | 50 | def I(attr): 51 | return range_property(attr, 0, 4294967295) 52 | 53 | 54 | def ntuple_range(attr, n, min, max): 55 | f = lambda x: isinstance(x, int_types) and min <= x <= max 56 | 57 | def getter(obj): 58 | return getattr(obj, "_%s" % attr) 59 | 60 | def setter(obj, val): 61 | if len(val) != n: 62 | raise ValueError( 63 | "Attribute '%s' must be tuple with %d elements [%s]" % (attr, n, val) 64 | ) 65 | if all(map(f, val)): 66 | setattr(obj, "_%s" % attr, val) 67 | else: 68 | raise ValueError( 69 | "Attribute '%s' elements must be between %d-%d [%s]" 70 | % (attr, min, max, val) 71 | ) 72 | 73 | return property(getter, setter) 74 | 75 | 76 | def IP4(attr): 77 | return ntuple_range(attr, 4, 0, 255) 78 | 79 | 80 | def IP6(attr): 81 | return ntuple_range(attr, 16, 0, 255) 82 | -------------------------------------------------------------------------------- /copyparty/stolen/ifaddr/README.md: -------------------------------------------------------------------------------- 1 | `ifaddr` with py2.7 support enabled by make-sfx.sh which strips py3 hints using strip_hints and removes the `^if True:` blocks 2 | 3 | L: BSD-2-Clause 4 | Copyright (c) 2014 Stefan C. Mueller 5 | https://github.com/pydron/ifaddr/ 6 | -------------------------------------------------------------------------------- /copyparty/stolen/ifaddr/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | """ 5 | L: BSD-2-Clause 6 | Copyright (c) 2014 Stefan C. Mueller 7 | https://github.com/pydron/ifaddr/tree/0.2.0 8 | """ 9 | 10 | import os 11 | 12 | from ._shared import IP, Adapter 13 | 14 | 15 | def nope(include_unconfigured=False): 16 | return [] 17 | 18 | 19 | try: 20 | S390X = os.uname().machine == "s390x" 21 | except: 22 | S390X = False 23 | 24 | 25 | if os.environ.get("PRTY_NO_IFADDR") or S390X: 26 | # s390x deadlocks at libc.getifaddrs 27 | get_adapters = nope 28 | elif os.name == "nt": 29 | from ._win32 import get_adapters 30 | elif os.name == "posix": 31 | from ._posix import get_adapters 32 | else: 33 | raise RuntimeError("Unsupported Operating System: %s" % os.name) 34 | 35 | __all__ = ["Adapter", "IP", "get_adapters"] 36 | -------------------------------------------------------------------------------- /copyparty/web/Makefile: -------------------------------------------------------------------------------- 1 | # run me to zopfli all the static files 2 | # which should help on really slow connections 3 | # but then why are you using copyparty in the first place 4 | 5 | pk: $(addsuffix .gz, $(wildcard *.js *.css)) 6 | un: $(addsuffix .un, $(wildcard *.gz)) 7 | 8 | %.gz: % 9 | pigz -11 -J 34 -I 573 $< 10 | 11 | %.un: % 12 | pigz -d $< 13 | -------------------------------------------------------------------------------- /copyparty/web/a/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/a/__init__.py -------------------------------------------------------------------------------- /copyparty/web/a/partyfuse.py: -------------------------------------------------------------------------------- 1 | ../../../bin/partyfuse.py -------------------------------------------------------------------------------- /copyparty/web/a/u2c.py: -------------------------------------------------------------------------------- 1 | ../../../bin/u2c.py -------------------------------------------------------------------------------- /copyparty/web/a/webdav-cfg.bat: -------------------------------------------------------------------------------- 1 | ../../../contrib/webdav-cfg.bat -------------------------------------------------------------------------------- /copyparty/web/browser2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 14 | 15 | 16 | 17 | {%- if srv_info %} 18 |

{{ srv_info }}

19 | {%- endif %} 20 | 21 | {%- if have_b_u %} 22 |
23 | 24 |
25 | 26 |
27 |
28 | {%- endif %} 29 | 30 | {%- if logues[0] %} 31 |
{{ logues[0] }}

32 | {%- endif %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {%- for f in files %} 47 | 50 | {%- endfor %} 51 | 52 | 53 |
cFile NameSizeDate
parent folder--
{{ f.lead }}{{ f.name|e }}{{ f.sz }}{{ f.dt }}
54 | 55 | {%- if logues[1] %} 56 |
{{ logues[1] }}

57 | {%- endif %} 58 | 59 |

control-panel

60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /copyparty/web/cf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ s_doctitle }} 7 | 8 | 9 | 10 | 11 | 12 |
13 |

please press F5 to reload the page

14 |

sorry for the inconvenience

15 |
16 | 17 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /copyparty/web/copyparty.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/copyparty.gif -------------------------------------------------------------------------------- /copyparty/web/dbg-audio.js: -------------------------------------------------------------------------------- 1 | var ofun = audio_eq.apply.bind(audio_eq); 2 | audio_eq.apply = function () { 3 | var ac1 = mp.ac; 4 | ofun(); 5 | var ac = mp.ac, 6 | w = 2048, 7 | h = 256; 8 | 9 | if (!audio_eq.filters.length) { 10 | audio_eq.ana = null; 11 | return; 12 | } 13 | 14 | var can = ebi('fft_can'); 15 | if (!can) { 16 | can = mknod('canvas', 'fft_can'); 17 | can.style.cssText = 'position:absolute;left:0;bottom:5em;width:' + w + 'px;height:' + h + 'px;z-index:9001'; 18 | document.body.appendChild(can); 19 | can.width = w; 20 | can.height = h; 21 | } 22 | var cc = can.getContext('2d'); 23 | if (!ac) 24 | return; 25 | 26 | var ana = ac.createAnalyser(); 27 | ana.smoothingTimeConstant = 0; 28 | ana.fftSize = 8192; 29 | 30 | audio_eq.filters[0].connect(ana); 31 | audio_eq.ana = ana; 32 | 33 | var buf = new Uint8Array(ana.frequencyBinCount), 34 | colw = can.width / buf.length; 35 | 36 | cc.fillStyle = '#fc0'; 37 | function draw() { 38 | if (ana == audio_eq.ana) 39 | requestAnimationFrame(draw); 40 | 41 | ana.getByteFrequencyData(buf); 42 | 43 | cc.clearRect(0, 0, can.width, can.height); 44 | 45 | /*var x = 0, w = 1; 46 | for (var a = 0; a < buf.length; a++) { 47 | cc.fillRect(x, h - buf[a], w, h); 48 | x += w; 49 | }*/ 50 | var mul = Math.pow(w, 4) / buf.length; 51 | for (var x = 0; x < w; x++) { 52 | var a = Math.floor(Math.pow(x, 4) / mul), 53 | v = buf[a]; 54 | 55 | cc.fillRect(x, h - v, 1, v); 56 | } 57 | } 58 | draw(); 59 | }; 60 | audio_eq.apply(); 61 | -------------------------------------------------------------------------------- /copyparty/web/dd/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/dd/2.png -------------------------------------------------------------------------------- /copyparty/web/dd/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/dd/3.png -------------------------------------------------------------------------------- /copyparty/web/dd/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/dd/4.png -------------------------------------------------------------------------------- /copyparty/web/dd/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/dd/5.png -------------------------------------------------------------------------------- /copyparty/web/dd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/dd/__init__.py -------------------------------------------------------------------------------- /copyparty/web/deps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/deps/__init__.py -------------------------------------------------------------------------------- /copyparty/web/iiam.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/copyparty/web/iiam.gif -------------------------------------------------------------------------------- /copyparty/web/mde.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 📝 {{ title }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ html_head }} 12 | 13 | 14 |
15 |
16 |
17 |
18 |
Loading
19 | if you're still reading this, check that javascript is allowed 20 |
21 |
22 |
23 | 24 |
25 |
26 | π 27 | 52 | 53 | 54 | 55 | 56 | {%- if js %} 57 | 58 | {%- endif %} 59 | 60 | 61 | -------------------------------------------------------------------------------- /copyparty/web/msg.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-main: sans-serif; 3 | --font-serif: serif; 4 | --font-mono: 'scp'; 5 | } 6 | html,body,tr,th,td,#files,a { 7 | color: inherit; 8 | background: none; 9 | font-weight: inherit; 10 | font-size: inherit; 11 | padding: 0; 12 | border: none; 13 | } 14 | html { 15 | color: #ccc; 16 | background: #333; 17 | font-family: sans-serif; 18 | font-family: var(--font-main), sans-serif; 19 | text-shadow: 1px 1px 0px #000; 20 | touch-action: manipulation; 21 | } 22 | html, body { 23 | margin: 0; 24 | padding: 0; 25 | } 26 | #box { 27 | padding: .5em 1em; 28 | background: #2c2c2c; 29 | } 30 | pre { 31 | font-family: monospace, monospace; 32 | font-family: var(--font-mono), monospace, monospace; 33 | } 34 | a { 35 | color: #fc5; 36 | } 37 | -------------------------------------------------------------------------------- /copyparty/web/msg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ s_doctitle }} 7 | 8 | 9 | 10 | 11 | {{ html_head }} 12 | 13 | 14 | 15 |
16 | 17 | {%- if h1 %} 18 |

{{ h1 }}

19 | {%- endif %} 20 | 21 | {%- if h2 %} 22 |

{{ h2 }}

23 | {%- endif %} 24 | 25 | {%- if p %} 26 |

{{ p }}

27 | {%- endif %} 28 | 29 | {%- if pre %} 30 |
{{ pre }}
31 | {%- endif %} 32 | 33 | {%- if html %} 34 | {{ html }} 35 | {%- endif %} 36 | 37 | {%- if click %} 38 | 39 | {%- endif %} 40 |
41 | 42 | {%- if redir %} 43 | 48 | {%- endif %} 49 | {%- if js %} 50 | 51 | {%- endif %} 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /copyparty/web/rups.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ s_doctitle }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{ html_head }} 14 | 15 | 16 | 17 |
18 | refresh 19 | control-panel 20 |   Filter: 21 |   22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
sizewhowhenagedirfile
30 |
31 | π 32 | 42 | 43 | 44 | 45 | {%- if js %} 46 | 47 | {%- endif %} 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /copyparty/web/rups.js: -------------------------------------------------------------------------------- 1 | function render() { 2 | var ups = V.ups, now = V.now, html = []; 3 | ebi('filter').value = V.filter; 4 | ebi('hits').innerHTML = 'showing ' + ups.length + ' files'; 5 | 6 | for (var a = 0; a < ups.length; a++) { 7 | var f = ups[a], 8 | vsp = vsplit(f.vp.split('?')[0]), 9 | dn = esc(uricom_dec(vsp[0])), 10 | fn = esc(uricom_dec(vsp[1])), 11 | at = f.at, 12 | td = now - f.at, 13 | ts = !at ? '(?)' : unix2iso(at), 14 | sa = !at ? '(?)' : td > 60 ? shumantime(td) : (td + 's'), 15 | sz = ('' + f.sz).replace(/\B(?=(\d{3})+(?!\d))/g, " "); 16 | 17 | html.push('' + sz + 18 | '' + f.ip + 19 | '' + ts + 20 | '' + sa + 21 | '' + dn + 22 | '' + fn + 23 | ''); 24 | } 25 | if (!ups.length) { 26 | var t = V.filter ? ' matching the filter' : ''; 27 | html = ['there are no uploads' + t + '']; 28 | } 29 | ebi('tb').innerHTML = html.join(''); 30 | } 31 | render(); 32 | 33 | var ti; 34 | function ask(e) { 35 | ev(e); 36 | clearTimeout(ti); 37 | ebi('hits').innerHTML = 'Loading...'; 38 | 39 | var xhr = new XHR(), 40 | filter = unsmart(ebi('filter').value); 41 | 42 | hist_replace(get_evpath().split('?')[0] + '?ru&filter=' + uricom_enc(filter)); 43 | 44 | xhr.onload = xhr.onerror = function () { 45 | try { 46 | V = JSON.parse(this.responseText) 47 | } 48 | catch (ex) { 49 | ebi('tb').innerHTML = 'failed to decode server response as json:
' + esc(this.responseText) + '
'; 50 | return; 51 | } 52 | render(); 53 | }; 54 | xhr.open('GET', SR + '/?ru&j&filter=' + uricom_enc(filter), true); 55 | xhr.send(); 56 | } 57 | ebi('re').onclick = ask; 58 | ebi('filter').oninput = function () { 59 | clearTimeout(ti); 60 | ti = setTimeout(ask, 500); 61 | ebi('hits').innerHTML = '...'; 62 | }; 63 | ebi('filter').onkeydown = function (e) { 64 | if (('' + e.key).endsWith('Enter')) 65 | ask(); 66 | }; 67 | -------------------------------------------------------------------------------- /copyparty/web/shares.css: -------------------------------------------------------------------------------- 1 | html { 2 | color: #333; 3 | background: #f7f7f7; 4 | font-family: sans-serif; 5 | font-family: var(--font-main), sans-serif; 6 | touch-action: manipulation; 7 | } 8 | #wrap { 9 | margin: 2em auto; 10 | padding: 0 1em 3em 1em; 11 | line-height: 2.3em; 12 | } 13 | #wrap>span { 14 | margin: 0 0 0 1em; 15 | border-bottom: 1px solid #999; 16 | } 17 | li { 18 | margin: 1em 0; 19 | } 20 | a { 21 | color: #047; 22 | background: #fff; 23 | text-decoration: none; 24 | white-space: nowrap; 25 | border-bottom: 1px solid #8ab; 26 | border-radius: .2em; 27 | padding: .2em .6em; 28 | margin: 0 .3em; 29 | } 30 | #wrap td a { 31 | margin: 0; 32 | } 33 | #w { 34 | color: #fff; 35 | background: #940; 36 | border-color: #b70; 37 | } 38 | #repl { 39 | border: none; 40 | background: none; 41 | color: inherit; 42 | padding: 0; 43 | position: fixed; 44 | bottom: .25em; 45 | left: .2em; 46 | } 47 | #wrap table { 48 | border-collapse: collapse; 49 | position: relative; 50 | margin-top: 2em; 51 | } 52 | th { 53 | top: -1px; 54 | position: sticky; 55 | background: #f7f7f7; 56 | } 57 | #wrap td, 58 | #wrap th { 59 | padding: .3em .6em; 60 | text-align: left; 61 | white-space: nowrap; 62 | } 63 | #wrap td+td+td+td+td+td+td+td { 64 | font-family: var(--font-mono), monospace, monospace; 65 | } 66 | #wrap th:first-child, 67 | #wrap td:first-child { 68 | border-radius: .5em 0 0 .5em; 69 | } 70 | #wrap th:last-child, 71 | #wrap td:last-child { 72 | border-radius: 0 .5em .5em 0; 73 | } 74 | 75 | 76 | 77 | html.z { 78 | background: #222; 79 | color: #ccc; 80 | } 81 | html.z a { 82 | color: #fff; 83 | background: #057; 84 | border-color: #37a; 85 | } 86 | html.z th { 87 | background: #222; 88 | } 89 | html.bz { 90 | color: #bbd; 91 | background: #11121d; 92 | } 93 | html.bz th { 94 | background: #223; 95 | } 96 | -------------------------------------------------------------------------------- /copyparty/web/svcs.js: -------------------------------------------------------------------------------- 1 | var oa = QSA('pre'); 2 | for (var a = 0; a < oa.length; a++) { 3 | var html = oa[a].innerHTML, 4 | nd = /^ +/.exec(html)[0].length, 5 | rd = new RegExp('(^|\r?\n) {' + nd + '}', 'g'); 6 | 7 | oa[a].innerHTML = html.replace(rd, '$1').replace(/[ \r\n]+$/, '').replace(/\r?\n/g, '
'); 8 | } 9 | 10 | function add_dls() { 11 | oa = QSA('pre.dl'); 12 | for (var a = 0; a < oa.length; a++) { 13 | var an = 'ta' + a, 14 | o = ebi(an) || mknod('a', an, 'download'); 15 | 16 | oa[a].setAttribute('id', 'tx' + a); 17 | oa[a].parentNode.insertBefore(o, oa[a]); 18 | o.setAttribute('download', oa[a].getAttribute('name')); 19 | o.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(oa[a].innerText)); 20 | clmod(o, 'txa', 1); 21 | } 22 | } 23 | add_dls(); 24 | 25 | 26 | oa = QSA('.ossel a'); 27 | for (var a = 0; a < oa.length; a++) 28 | oa[a].onclick = esetos; 29 | 30 | function esetos(e) { 31 | ev(e); 32 | setos(((e && e.target) || (window.event && window.event.srcElement)).id.slice(1)); 33 | } 34 | 35 | function setos(os) { 36 | var oa = QSA('.os'); 37 | for (var a = 0; a < oa.length; a++) 38 | oa[a].style.display = 'none'; 39 | 40 | var oa = QSA('.' + os); 41 | for (var a = 0; a < oa.length; a++) 42 | oa[a].style.display = ''; 43 | 44 | oa = QSA('.ossel a'); 45 | for (var a = 0; a < oa.length; a++) 46 | clmod(oa[a], 'g', oa[a].id.slice(1) == os); 47 | } 48 | 49 | setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk'); 50 | 51 | 52 | ebi('setpw').onclick = function (e) { 53 | ev(e); 54 | modal.prompt('password:', '', function (v) { 55 | if (!v) 56 | return; 57 | 58 | var pw0 = ebi('pw0').innerHTML, 59 | oa = QSA('b'); 60 | 61 | for (var a = 0; a < oa.length; a++) 62 | if (oa[a].innerHTML == pw0) 63 | oa[a].textContent = v; 64 | 65 | add_dls(); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** there's more stuff (sharex config, service scripts, nginx configs, ...) in [`/contrib/`](/contrib/) 2 | 3 | 4 | 5 | # utilities 6 | 7 | ## [`multisearch.html`](multisearch.html) 8 | * takes a list of filenames of youtube rips, grabs the youtube-id of each file, and does a search on the server for those 9 | * use it by putting it somewhere on the server and opening it as an html page 10 | * also serves as an extendable template for other specific search behaviors 11 | 12 | 13 | 14 | # other stuff 15 | 16 | ## [`TODO.md`](TODO.md) 17 | * planned features / fixes / changes 18 | 19 | ## [`example.conf`](example.conf) 20 | * example config file for `-c` 21 | 22 | ## [`versus.md`](versus.md) 23 | * similar software / alternatives (with pros/cons) 24 | 25 | ## [`changelog.md`](changelog.md) 26 | * occasionally grabbed from github release notes 27 | 28 | ## [`synology-dsm.md`](synology-dsm.md) 29 | * running copyparty on a synology nas 30 | 31 | ## [`devnotes.md`](devnotes.md) 32 | * technical stuff 33 | 34 | ## [`rclone.md`](rclone.md) 35 | * notes on using rclone as a fuse client/server 36 | 37 | 38 | 39 | # junk 40 | 41 | alphabetical list of the remaining files 42 | 43 | | what | why | 44 | | -- | -- | 45 | | [biquad.html](biquad.html) | bruteforce calibrator for the audio equalizer since im not that good at maths | 46 | | [design.txt](design.txt) | initial brainstorming of the copyparty design, unmaintained, incorrect, sentimental value only | 47 | | [hls.html](hls.html) | experimenting with hls playback using `hls.js`, works p well, almost became a thing | 48 | | [music-analysis.sh](music-analysis.sh) | testing various bpm/key detection libraries before settling on the ones used in [`/bin/mtag/`](/bin/mtag/) | 49 | | [notes.sh](notes.sh) | notepad, just scraps really | 50 | | [nuitka.txt](nuitka.txt) | how to build a copyparty exe using nuitka (not maintained) | 51 | | [pretend-youre-qnap.patch](pretend-youre-qnap.patch) | simulate a NAS which keeps returning old cached data even though you just modified the file yourself | 52 | | [tcp-debug.sh](tcp-debug.sh) | looks like this was to debug stuck tcp connections? | 53 | | [unirange.py](unirange.py) | uhh | 54 | | [up2k.txt](up2k.txt) | initial ideas for how up2k should work, another unmaintained sentimental-value-only thing | 55 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | a living list of upcoming features / fixes / changes, very roughly in order of priority 2 | 3 | * download accelerator 4 | * definitely download chunks in parallel 5 | * maybe resumable downloads (chrome-only, jank api) 6 | * maybe checksum validation (return sha512 of requested range in responses, and probably also warks) 7 | 8 | * [github issue #37](https://github.com/9001/copyparty/issues/37) - upload PWA 9 | * or [maybe not](https://arstechnica.com/tech-policy/2024/02/apple-under-fire-for-disabling-iphone-web-apps-eu-asks-developers-to-weigh-in/), or [maybe](https://arstechnica.com/gadgets/2024/03/apple-changes-course-will-keep-iphone-eu-web-apps-how-they-are-in-ios-17-4/) 10 | 11 | * [github issue #57](https://github.com/9001/copyparty/issues/57) - config GUI 12 | * configs given to -c can be ordered with numerical prefix 13 | * autorevert settings if it fails to apply 14 | * countdown until session invalidates in settings gui, with refresh-button 15 | 16 | -------------------------------------------------------------------------------- /docs/chunksizes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # there's far better ways to do this but its 4am and i dont wanna think 4 | 5 | # just pypy it my dude 6 | 7 | import math 8 | 9 | def humansize(sz, terse=False): 10 | for unit in ["B", "KiB", "MiB", "GiB", "TiB"]: 11 | if sz < 1024: 12 | break 13 | 14 | sz /= 1024.0 15 | 16 | ret = " ".join([str(sz)[:4].rstrip("."), unit]) 17 | 18 | if not terse: 19 | return ret 20 | 21 | return ret.replace("iB", "").replace(" ", "") 22 | 23 | 24 | def up2k_chunksize(filesize): 25 | chunksize = 1024 * 1024 26 | stepsize = 512 * 1024 27 | while True: 28 | for mul in [1, 2]: 29 | nchunks = math.ceil(filesize * 1.0 / chunksize) 30 | if nchunks <= 256 or (chunksize >= 32 * 1024 * 1024 and nchunks <= 4096): 31 | return chunksize 32 | 33 | chunksize += stepsize 34 | stepsize *= mul 35 | 36 | 37 | def main(): 38 | prev = 1048576 39 | n = n0 = 524288 40 | while True: 41 | csz = up2k_chunksize(n) 42 | if csz > prev: 43 | print(f"| {n-n0:>18_} | {humansize(n-n0):>8} | {prev:>13_} | {humansize(prev):>8} |".replace("_", " ")) 44 | prev = csz 45 | n += n0 46 | 47 | 48 | main() 49 | -------------------------------------------------------------------------------- /docs/copyparty.d/foo/another.conf: -------------------------------------------------------------------------------- 1 | # this file gets included twice from ../some.conf, 2 | # setting user permissions for a volume 3 | accs: 4 | rw: usr1 5 | r: usr2 6 | % sibling.conf 7 | -------------------------------------------------------------------------------- /docs/copyparty.d/foo/sibling.conf: -------------------------------------------------------------------------------- 1 | # and this config file gets included from ./another.conf, 2 | # adding a final permission for each of the two volumes in ../some.conf 3 | m: usr1, usr2 4 | -------------------------------------------------------------------------------- /docs/copyparty.d/some.conf: -------------------------------------------------------------------------------- 1 | # not actually YAML but lets pretend: 2 | # -*- mode: yaml -*- 3 | # vim: ft=yaml: 4 | 5 | # lets make two volumes with the same accounts/permissions for both; 6 | # first declare the accounts just once: 7 | [accounts] 8 | usr1: passw0rd 9 | usr2: letmein 10 | 11 | [global] 12 | i: 127.0.0.1 # listen on 127.0.0.1 only, 13 | p: 2434 # port 2434 14 | e2ds # enable file indexing+scanning 15 | e2ts # and multimedia indexing+scanning 16 | # (inline comments are OK if there is 2 spaces before the #) 17 | 18 | # share /usr/share/games from the server filesystem 19 | [/vidya] 20 | /usr/share/games 21 | % foo/another.conf # include config file with volume permissions 22 | 23 | # and share your ~/Music folder too 24 | [/bangers] 25 | ~/Music 26 | % foo/another.conf 27 | 28 | # which should result in each of the volumes getting the following permissions: 29 | # usr1 read/write/move 30 | # usr2 read/move 31 | # 32 | # because another.conf sets the read/write permissions before it 33 | # includes sibling.conf which adds the move permission 34 | -------------------------------------------------------------------------------- /docs/cursed-usecases/README.md: -------------------------------------------------------------------------------- 1 | insane ways to use copyparty 2 | 3 | 4 | ## wireless keyboard 5 | 6 | problem: you wanna control mpv or whatever software from the couch but you don't have a wireless keyboard 7 | 8 | "solution": load some custom javascript which renders a virtual keyboard on the upload UI and each keystroke is actually an upload which gets picked up by a dummy metadata parser which forwards the keystrokes into xdotool 9 | 10 | [no joke, this actually exists and it wasn't even my idea or handiwork (thx steen)](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js) 11 | 12 | 13 | ## appxsvc tarpit 14 | 15 | problem: `svchost.exe` is using 100% of a cpu core, and upon further inspection (`procmon`) it is `wsappx` desperately trying to install something, repeatedly reading a file named `AppxManifest.xml` and messing with an sqlite3 database 16 | 17 | "solution": create a virtual filesystem which is intentionally slow and trick windows into reading it from there instead 18 | 19 | * create a file called `AppxManifest.xml` and put something dumb in it 20 | * serve the file from a copyparty instance with `--rsp-slp=1` so every request will hang for 1 sec 21 | * `net use m: http://127.0.0.1:3993/` (mount copyparty using the windows-native webdav client) 22 | * `mklink /d c:\windows\systemapps\microsoftwindows.client.cbs_cw5n1h2txyewy\AppxManifest.xml m:\AppxManifest.xml` 23 | -------------------------------------------------------------------------------- /docs/design.txt: -------------------------------------------------------------------------------- 1 | ## 2 | ## thumbnails 3 | 4 | two components: 5 | thumbcache is directly accessed by httpd to return pregenerated thumbnails 6 | thumbsrv is accessed through a broker on cache miss to generate and return 7 | 8 | ## 9 | ## initial ideas 10 | 11 | need log interface 12 | tcpsrv creates it 13 | httpsrv must use interface 14 | 15 | msgsvc 16 | simulates a multiprocessing queue 17 | takes events from httpsrv 18 | logging 19 | mpsrv pops queue and forwards to this 20 | 21 | tcpsrv 22 | tcp listener 23 | pass tcp clients to worker 24 | api to get status messages from workers 25 | 26 | mpsrv 27 | uses multiprocessing to handle incoming clients 28 | 29 | httpsrv 30 | takes client sockets, starts threads 31 | takes argv acc/vol through init args 32 | loads acc/vol from config file 33 | -------------------------------------------------------------------------------- /docs/example2.conf: -------------------------------------------------------------------------------- 1 | # you can include additional config like this 2 | # (the space after the % is important) 3 | # 4 | # since copyparty.d is a folder, it'll include all *.conf 5 | # files inside (not recursively) in alphabetical order 6 | # (not necessarily same as numerical/natural order) 7 | # 8 | # paths are relative from the location of each included file 9 | # unless the path is absolute, for example % /etc/copyparty.d 10 | # 11 | # max include depth is 64 12 | 13 | % copyparty.d 14 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | copyparty server config examples 2 | 3 | [windows.md](windows.md) -- running copyparty as a service on windows 4 | 5 | -------------------------------------------------------------------------------- /docs/examples/docker/basic-docker-compose/copyparty.conf: -------------------------------------------------------------------------------- 1 | # not actually YAML but lets pretend: 2 | # -*- mode: yaml -*- 3 | # vim: ft=yaml: 4 | 5 | 6 | [global] 7 | e2dsa # enable file indexing and filesystem scanning 8 | e2ts # enable multimedia indexing 9 | ansi # enable colors in log messages 10 | 11 | # q, lo: /cfg/log/%Y-%m%d.log # log to file instead of docker 12 | 13 | # p: 3939 # listen on another port 14 | # ipa: 10.89. # only allow connections from 10.89.* 15 | # df: 16 # stop accepting uploads if less than 16 GB free disk space 16 | # ver # show copyparty version in the controlpanel 17 | # grid # show thumbnails/grid-view by default 18 | # theme: 2 # monokai 19 | # name: datasaver # change the server-name that's displayed in the browser 20 | # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) 21 | # no-robots, force-js # make it harder for search engines to read your server 22 | 23 | 24 | [accounts] 25 | ed: wark # username: password 26 | 27 | 28 | [/] # create a volume at "/" (the webroot), which will 29 | /w # share /w (the docker data volume) 30 | accs: 31 | rw: * # everyone gets read-write access, but 32 | rwmda: ed # the user "ed" gets read-write-move-delete-admin 33 | -------------------------------------------------------------------------------- /docs/examples/docker/basic-docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | copyparty: 5 | image: copyparty/ac:latest 6 | container_name: copyparty 7 | user: "1000:1000" 8 | ports: 9 | - 3923:3923 10 | volumes: 11 | - ./:/cfg:z 12 | - /path/to/your/fileshare/top/folder:/w:z 13 | 14 | # enabling mimalloc by replacing "NOPE" with "2" will make some stuff twice as fast, but everything will use twice as much ram: 15 | environment: 16 | LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE 17 | 18 | stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal 19 | healthcheck: 20 | # hide it from logs with "/._" so it matches the default --lf-url filter 21 | test: ["CMD-SHELL", "wget --spider -q 127.0.0.1:3923/?reset=/._"] 22 | interval: 1m 23 | timeout: 2s 24 | retries: 5 25 | start_period: 15s 26 | -------------------------------------------------------------------------------- /docs/examples/docker/idp-authelia-traefik/authelia/configuration.yml: -------------------------------------------------------------------------------- 1 | # based on https://github.com/authelia/authelia/blob/39763aaed24c4abdecd884b47357a052b235942d/examples/compose/lite/authelia/configuration.yml 2 | 3 | # Authelia configuration 4 | 5 | # This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE 6 | jwt_secret: a_very_important_secret 7 | 8 | server: 9 | address: 'tcp://:9091' 10 | 11 | log: 12 | level: info # debug 13 | 14 | totp: 15 | issuer: authelia.com 16 | 17 | authentication_backend: 18 | file: 19 | path: /config/users_database.yml 20 | 21 | access_control: 22 | default_policy: deny 23 | rules: 24 | # Rules applied to everyone 25 | - domain: traefik.example.com 26 | policy: one_factor 27 | - domain: fs.example.com 28 | policy: one_factor 29 | 30 | session: 31 | # This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE 32 | secret: unsecure_session_secret 33 | 34 | cookies: 35 | - name: authelia_session 36 | domain: example.com # Should match whatever your root protected domain is 37 | default_redirection_url: https://fs.example.com 38 | authelia_url: https://authelia.example.com/ 39 | expiration: 3600 # 1 hour 40 | inactivity: 300 # 5 minutes 41 | 42 | redis: 43 | host: redis 44 | port: 6379 45 | # This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD_FILE 46 | # password: authelia 47 | 48 | regulation: 49 | max_retries: 3 50 | find_time: 120 51 | ban_time: 300 52 | 53 | storage: 54 | encryption_key: you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this 55 | local: 56 | path: /config/db.sqlite3 57 | 58 | notifier: 59 | disable_startup_check: true 60 | smtp: 61 | username: test 62 | # This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE 63 | password: password 64 | host: mail.example.com 65 | port: 25 66 | sender: admin@example.com 67 | -------------------------------------------------------------------------------- /docs/examples/docker/idp-authelia-traefik/authelia/users_database.yml: -------------------------------------------------------------------------------- 1 | # based on https://github.com/authelia/authelia/blob/39763aaed24c4abdecd884b47357a052b235942d/examples/compose/lite/authelia/users_database.yml 2 | 3 | # Users Database 4 | 5 | # This file can be used if you do not have an LDAP set up. 6 | 7 | # List of users 8 | users: 9 | authelia: 10 | disabled: false 11 | displayname: "Authelia User" 12 | # Password is authelia 13 | password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" 14 | email: authelia@authelia.com 15 | groups: 16 | - admins 17 | - dev 18 | - su 19 | -------------------------------------------------------------------------------- /docs/examples/docker/idp-authentik-traefik/README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > I am unable to guarantee the quality, safety, and security of anything in this folder; it is a combination of examples I found online. Please submit corrections or improvements 🙏 3 | 4 | > [!WARNING] 5 | > does not work yet... if you are able to fix this, please do! 6 | 7 | this is based on: 8 | * https://goauthentik.io/docker-compose.yml 9 | * https://goauthentik.io/docs/providers/proxy/server_traefik 10 | 11 | incomplete list of modifications made: 12 | * support for running with podman as root on fedora (`:z` volumes, `label:disable`) 13 | -------------------------------------------------------------------------------- /docs/examples/docker/idp-authentik-traefik/based-on/docker-compose-traefik.yml: -------------------------------------------------------------------------------- 1 | # https://goauthentik.io/docs/providers/proxy/server_traefik 2 | --- 3 | version: "3.7" 4 | services: 5 | traefik: 6 | image: traefik:v2.2 7 | container_name: traefik 8 | volumes: 9 | - /var/run/docker.sock:/var/run/docker.sock 10 | ports: 11 | - 80:80 12 | command: 13 | - "--api" 14 | - "--providers.docker=true" 15 | - "--providers.docker.exposedByDefault=false" 16 | - "--entrypoints.web.address=:80" 17 | 18 | authentik-proxy: 19 | image: ghcr.io/goauthentik/proxy 20 | ports: 21 | - 9000:9000 22 | - 9443:9443 23 | environment: 24 | AUTHENTIK_HOST: https://your-authentik.tld 25 | AUTHENTIK_INSECURE: "false" 26 | AUTHENTIK_TOKEN: token-generated-by-authentik 27 | # Starting with 2021.9, you can optionally set this too 28 | # when authentik_host for internal communication doesn't match the public URL 29 | # AUTHENTIK_HOST_BROWSER: https://external-domain.tld 30 | labels: 31 | traefik.enable: true 32 | traefik.port: 9000 33 | traefik.http.routers.authentik.rule: Host(`app.company`) && PathPrefix(`/outpost.goauthentik.io/`) 34 | # `authentik-proxy` refers to the service name in the compose file. 35 | traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/outpost.goauthentik.io/auth/traefik 36 | traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true 37 | traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version 38 | restart: unless-stopped 39 | 40 | whoami: 41 | image: containous/whoami 42 | labels: 43 | traefik.enable: true 44 | traefik.http.routers.whoami.rule: Host(`app.company`) 45 | traefik.http.routers.whoami.middlewares: authentik@docker 46 | restart: unless-stopped 47 | -------------------------------------------------------------------------------- /docs/examples/docker/portainer.md: -------------------------------------------------------------------------------- 1 | the following setup appears to work (copyparty starts, accepts uploads, is able to persist config) 2 | 3 | tested on debian 12 using [portainer-ce](https://docs.portainer.io/start/install-ce/server/docker/linux) with [docker-ce](https://docs.docker.com/engine/install/debian/) as root (not rootless) 4 | 5 | before making the container, first `mkdir /etc/copyparty /srv/pub` which will be bind-mounts into the container 6 | 7 | > both `/etc/copyparty` and `/srv/pub` are examples; you can change them if you'd like 8 | 9 | put your copyparty config files directly into `/etc/copyparty` and the files to share inside `/srv/pub` 10 | 11 | on first startup, copyparty will create a subfolder inside `/etc/copyparty` called `copyparty` where it puts some runtime state; for example replacing `/etc/copyparty/copyparty/cert.pem` with another TLS certificate is a quick and dirty way to get valid HTTPS (if you really want copyparty to handle that and not a reverse-proxy) 12 | 13 | 14 | ## in portainer: 15 | 16 | ``` 17 | environments -> local -> containers -> add container: 18 | 19 | name = copyparty-ac 20 | registry = docker hub 21 | image = copyparty/ac 22 | always pull = no 23 | 24 | manual network port publishing: 25 | 3923 to 3923 [TCP] 26 | 27 | advanced -> command & logging: 28 | console = interactive & tty 29 | 30 | advanced -> volumes -> map additional volume: 31 | container = /cfg [Bind] 32 | host = /etc/copyparty [Writable] 33 | 34 | advanced -> volumes -> map additional volume: 35 | container = /w [Bind] 36 | host = /srv/pub [Writable] 37 | ``` 38 | 39 | notes: 40 | 41 | * `/cfg` is where copyparty expects to find its config files; `/etc/copyparty` is just an example mapping to that 42 | 43 | * `/w` is where copyparty expects to find the folder to share; `/srv/pub` is just an example mapping to that 44 | 45 | * the volumes must be bind-mounts to avoid permission issues (or so the theory goes) 46 | -------------------------------------------------------------------------------- /docs/lics.txt: -------------------------------------------------------------------------------- 1 | --- server-side --- software --- 2 | 3 | https://github.com/9001/copyparty/ 4 | C: 2019 ed 5 | L: MIT 6 | 7 | https://github.com/pallets/jinja/ 8 | C: 2007 Pallets 9 | L: BSD 3-Clause 10 | 11 | https://github.com/pallets/markupsafe/ 12 | C: 2010 Pallets 13 | L: BSD 3-Clause 14 | 15 | https://github.com/paulc/dnslib/ 16 | C: 2010-2017 Paul Chakravarti 17 | L: BSD 2-Clause 18 | 19 | https://github.com/pydron/ifaddr/ 20 | C: 2014 Stefan C. Mueller 21 | L: BSD-2-Clause 22 | 23 | https://github.com/giampaolo/pyftpdlib/ 24 | C: 2007 Giampaolo Rodola 25 | L: MIT 26 | 27 | https://github.com/9001/partftpy 28 | C: 2010-2021 Michael P. Soulier 29 | L: MIT 30 | 31 | https://github.com/nayuki/QR-Code-generator/ 32 | C: Project Nayuki 33 | L: MIT 34 | 35 | https://github.com/ahupp/python-magic/ 36 | C: 2001-2014 Adam Hupp 37 | L: MIT 38 | 39 | --- client-side --- software --- 40 | 41 | https://github.com/Daninet/hash-wasm/ 42 | C: 2020 Dani Biró 43 | L: MIT 44 | 45 | https://github.com/openpgpjs/asmcrypto.js/ 46 | C: 2013 Artem S Vybornov 47 | L: MIT 48 | 49 | https://github.com/feimosi/baguetteBox.js/ 50 | C: 2017 Marek Grzybek 51 | L: MIT 52 | 53 | https://github.com/markedjs/marked/ 54 | C: 2018+, MarkedJS 55 | C: 2011-2018, Christopher Jeffrey (https://github.com/chjj/) 56 | L: MIT 57 | 58 | https://github.com/codemirror/codemirror5/ 59 | C: 2017 Marijn Haverbeke and others 60 | L: MIT 61 | 62 | https://github.com/Ionaru/easy-markdown-editor/ 63 | C: 2015 Sparksuite, Inc. 64 | C: 2017 Jeroen Akkerman. 65 | L: MIT 66 | 67 | --- client-side --- fonts --- 68 | 69 | https://github.com/adobe-fonts/source-code-pro/ 70 | C: 2010-2019 Adobe 71 | L: SIL OFL 1.1 72 | 73 | https://github.com/FortAwesome/Font-Awesome/ 74 | C: 2022 Fonticons, Inc. 75 | L: SIL OFL 1.1 76 | -------------------------------------------------------------------------------- /docs/notes.bat: -------------------------------------------------------------------------------- 1 | rem appending a static ip to a dhcp nic on windows 10-1703 or later 2 | netsh interface ipv4 show interface 3 | netsh interface ipv4 set interface interface="Ethernet 2" dhcpstaticipcoexistence=enabled 4 | netsh interface ipv4 add address "Ethernet 2" 10.1.2.4 255.255.255.0 5 | -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | this file accidentally got committed at some point, so let's put it to use 2 | 3 | # trivia / lore 4 | 5 | copyparty started as [three separate php projects](https://a.ocv.me/pub/stuff/old-php-projects/); an nginx custom directory listing (which became a php script), and a php music/picture viewer, and an additional php project for resumable uploads: 6 | 7 | * findex -- directory browser / gallery with thumbnails and a music player which sometime back in 2009 had a canvas visualizer grabbing fft data from a flash audio player 8 | * findex.mini -- plain-listing fork of findex with streaming zip-download of folders (the js and design should look familiar) 9 | * upper and up2k -- up2k being the star of the show and where copyparty's chunked resumable uploads came from 10 | 11 | the first link has screenshots but if that doesn't work there's also a [tar here](https://ocv.me/dev/old-php-projects.tgz) 12 | 13 | ---- 14 | 15 | below this point is misc useless scribbles 16 | 17 | # up2k.js 18 | 19 | ## potato detection 20 | 21 | * tsk 0.25/8.4/31.5 bzw 1.27/22.9/18 = 77% (38.4s, 49.7s) 22 | * 4c locale #1313, ff-102,deb-11 @ ryzen4500u wifi -> win10 23 | * profiling shows 2sec heavy gc every 2sec 24 | 25 | * tsk 0.41/4.1/10 bzw 1.41/9.9/7 = 73% (13.3s, 18.2s) 26 | * 4c locale #1313, ch-103,deb-11 @ ryzen4500u wifi -> win10 27 | -------------------------------------------------------------------------------- /docs/pretend-youre-qnap.patch: -------------------------------------------------------------------------------- 1 | diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py 2 | index 2d3c1ad..e1e85a0 100644 3 | --- a/copyparty/httpcli.py 4 | +++ b/copyparty/httpcli.py 5 | @@ -864,6 +864,30 @@ class HttpCli(object): 6 | # 7 | # send reply 8 | 9 | + try: 10 | + fakefn = self.conn.hsrv.fakefn 11 | + fakectr = self.conn.hsrv.fakectr 12 | + fakedata = self.conn.hsrv.fakedata 13 | + except: 14 | + fakefn = b'' 15 | + fakectr = 0 16 | + fakedata = b'' 17 | + 18 | + self.log('\n{} {}\n{}'.format(fakefn, fakectr, open_args[0])) 19 | + if fakefn == open_args[0] and fakectr > 0: 20 | + self.reply(fakedata, mime=guess_mime(req_path)[0]) 21 | + self.conn.hsrv.fakectr = fakectr - 1 22 | + else: 23 | + with open_func(*open_args) as f: 24 | + fakedata = f.read() 25 | + 26 | + self.conn.hsrv.fakefn = open_args[0] 27 | + self.conn.hsrv.fakedata = fakedata 28 | + self.conn.hsrv.fakectr = 15 29 | + self.reply(fakedata, mime=guess_mime(req_path)[0]) 30 | + 31 | + return True 32 | + 33 | self.out_headers["Accept-Ranges"] = "bytes" 34 | self.send_headers( 35 | length=upper - lower, 36 | -------------------------------------------------------------------------------- /docs/protocol-reference.sh: -------------------------------------------------------------------------------- 1 | vsftpd a.conf -olisten=YES -olisten_port=3921 -orun_as_launching_user=YES -obackground=NO -olog_ftp_protocol=YES 2 | 3 | -------------------------------------------------------------------------------- /docs/rice/rtl.patch: -------------------------------------------------------------------------------- 1 | RTL support is not planned, but it would be 2 | something like this (just a whole lot more) 3 | 4 | diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css 5 | index e66279d4..2888be56 100644 6 | --- a/copyparty/web/browser.css 7 | +++ b/copyparty/web/browser.css 8 | @@ -653,12 +653,10 @@ a:hover { 9 | .s0:after, 10 | .s1:after { 11 | content: '⌄'; 12 | - margin-left: -.15em; 13 | } 14 | .s0r:after, 15 | .s1r:after { 16 | content: '⌃'; 17 | - margin-left: -.15em; 18 | } 19 | .s0:after, 20 | .s0r:after { 21 | @@ -668,6 +666,19 @@ a:hover { 22 | .s1r:after { 23 | color: var(--sort-2); 24 | } 25 | +.ltr .s0:after, 26 | +.ltr .s1:after, 27 | +.ltr .s0r:after, 28 | +.ltr .s1r:after { 29 | + margin-left: -.15em; 30 | +} 31 | +.rtl .s0:after, 32 | +.rtl .s1:after, 33 | +.rtl .s0r:after, 34 | +.rtl .s1r:after { 35 | + margin-left: -.5em; 36 | + padding: 0 .25em 0 0; 37 | +} 38 | #files thead th:after { 39 | margin-right: -.5em; 40 | } 41 | diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js 42 | index 33965a70..bf425cc7 100644 43 | --- a/copyparty/web/browser.js 44 | +++ b/copyparty/web/browser.js 45 | @@ -1797,9 +1797,13 @@ var Ls = { 46 | 47 | "lang_set": "刷新以使更改生效?", 48 | }, 49 | + "foo": { 50 | + "tt": "Foobar", 51 | + "rtl": "rtl", 52 | + }, 53 | }; 54 | 55 | -var LANGS = ["eng", "nor", "chi"]; 56 | +var LANGS = ["eng", "nor", "chi", "foo"]; 57 | 58 | if (window.langmod) 59 | langmod(); 60 | @@ -1819,7 +1823,7 @@ for (var a = 0; a < LANGS.length; a++) { 61 | t2 = Ls[LANGS[i2]]; 62 | 63 | for (var k in t1) 64 | - if (!t2[k]) { 65 | + if (!t2[k] && k != 'rtl') { 66 | console.log("E missing TL", LANGS[i2], k); 67 | t2[k] = t1[k]; 68 | } 69 | @@ -1829,6 +1833,10 @@ for (var a = 0; a < LANGS.length; a++) { 70 | if (!has(LANGS, lang)) 71 | alert('unsupported --lang "' + lang + '" specified in server args;\nplease use one of these: ' + LANGS); 72 | 73 | +if (L.rtl) 74 | + document.documentElement.setAttribute('dir', L.rtl); 75 | +document.documentElement.className = L.rtl || 'ltr'; 76 | + 77 | modal.load(); 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/tcp-debug.sh: -------------------------------------------------------------------------------- 1 | (cd ~/dev/copyparty && strace -Tttyyvfs 256 -o strace.strace python3 -um copyparty -i 127.0.0.1 --http-only --stackmon /dev/shm/cpps,10 ) 2>&1 | tee /dev/stderr > ~/log-copyparty-$(date +%Y-%m%d-%H%M%S).txt 2 | 3 | 14/Jun/2021:16:34:02 1623688447.212405 death 4 | 14/Jun/2021:16:35:02 1623688502.420860 back 5 | 6 | tcpdump -nni lo -w /home/ed/lo.pcap 7 | 8 | # 16:35:25.324662 IP 127.0.0.1.48632 > 127.0.0.1.3920: Flags [F.], seq 849, ack 544, win 359, options [nop,nop,TS val 809396796 ecr 809396796], length 0 9 | 10 | tcpdump -nnr /home/ed/lo.pcap | awk '/ > 127.0.0.1.3920: /{sub(/ > .*/,"");sub(/.*\./,"");print}' | sort -n | uniq | while IFS= read -r port; do echo; tcpdump -nnr /home/ed/lo.pcap 2>/dev/null | grep -E "\.$port( > |: F)" | sed -r 's/ > .*, /, /'; done | grep -E '^16:35:0.*length [^0]' -C50 11 | 12 | 16:34:02.441732 IP 127.0.0.1.48638, length 0 13 | 16:34:02.441738 IP 127.0.0.1.3920, length 0 14 | 16:34:02.441744 IP 127.0.0.1.48638, length 0 15 | 16:34:02.441756 IP 127.0.0.1.48638, length 791 16 | 16:34:02.441759 IP 127.0.0.1.3920, length 0 17 | 16:35:02.445529 IP 127.0.0.1.48638, length 0 18 | 16:35:02.489194 IP 127.0.0.1.3920, length 0 19 | 16:35:02.515595 IP 127.0.0.1.3920, length 216 20 | 16:35:02.515600 IP 127.0.0.1.48638, length 0 21 | 22 | grep 48638 "$(find ~ -maxdepth 1 -name log-copyparty-\*.txt | sort | tail -n 1)" 23 | 24 | 1623688502.510380 48638 rh 25 | 1623688502.511291 48638 Unrecv direct ... 26 | 1623688502.511827 48638 rh = 791 27 | 16:35:02.518 127.0.0.1 48638 shut(8): [Errno 107] Socket not connected 28 | Exception in thread httpsrv-0.1-48638: 29 | 30 | grep 48638 ~/dev/copyparty/strace.strace 31 | 14561 16:35:02.506310 <... accept4 resumed> {sa_family=AF_INET, sin_port=htons(48638), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 8127.0.0.1:48638]> <0.000012> 32 | 15230 16:35:02.510725 write(1, "1623688502.510380 48638 rh\n", 27 33 | -------------------------------------------------------------------------------- /docs/unirange.py: -------------------------------------------------------------------------------- 1 | v = "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" 2 | for v in v.split(","): 3 | if "+" in v: 4 | v = v.split("+")[1] 5 | if "-" in v: 6 | lo, hi = v.split("-") 7 | else: 8 | lo = hi = v 9 | for v in range(int(lo, 16), int(hi, 16) + 1): 10 | print("{:4x} [{}]".format(v, chr(v))) 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1678901627, 6 | "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1748162331, 21 | "narHash": "sha256-rqc2RKYTxP3tbjA+PB3VMRQNnjesrT0pEofXQTrMsS8=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "7c43f080a7f28b2774f3b3f43234ca11661bf334", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "id": "nixpkgs", 29 | "ref": "nixos-25.05", 30 | "type": "indirect" 31 | } 32 | }, 33 | "root": { 34 | "inputs": { 35 | "flake-utils": "flake-utils", 36 | "nixpkgs": "nixpkgs" 37 | } 38 | } 39 | }, 40 | "root": "root", 41 | "version": 7 42 | } 43 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "nixpkgs/nixos-25.05"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = { self, nixpkgs, flake-utils }: 8 | { 9 | nixosModules.default = ./contrib/nixos/modules/copyparty.nix; 10 | overlays.default = self: super: { 11 | copyparty = 12 | self.python3.pkgs.callPackage ./contrib/package/nix/copyparty { 13 | ffmpeg = self.ffmpeg-full; 14 | }; 15 | }; 16 | } // flake-utils.lib.eachDefaultSystem (system: 17 | let 18 | pkgs = import nixpkgs { 19 | inherit system; 20 | config = { 21 | allowAliases = false; 22 | }; 23 | overlays = [ self.overlays.default ]; 24 | }; 25 | in { 26 | packages = { 27 | inherit (pkgs) copyparty; 28 | default = self.packages.${system}.copyparty; 29 | }; 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /scripts/copyparty-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | _msg() { printf "$2"'\033[1;30m>\033[0;33m>\033[1m>\033[0m %s\n' "$1" >&2; } 5 | imsg() { _msg "$1" ''; } 6 | msg() { _msg "$1" \\n; } 7 | 8 | ## 9 | ## helper which installs termux packages 10 | 11 | termux_upd=y 12 | addpkg() { 13 | t0=$(date +%s -r ~/../usr/var/cache/apt/pkgcache.bin 2>/dev/null || echo 0) 14 | t1=$(date +%s) 15 | 16 | [ $((t1-t0)) -gt 600 ] && { 17 | msg "upgrading termux packages" 18 | apt update 19 | apt full-upgrade -y 20 | } 21 | msg "installing $1 from termux repos" 22 | apt install -y $1 23 | } 24 | 25 | ## 26 | ## ensure python is available 27 | 28 | command -v python3 >/dev/null || 29 | addpkg python 30 | 31 | ## 32 | ## ensure virtualenv and dependencies are available 33 | 34 | ve=$HOME/ve.copyparty 35 | 36 | [ -e $ve/.ok ] || ( 37 | rm -rf $ve 38 | 39 | msg "creating python3 virtualenv" 40 | python3 -m venv $ve 41 | 42 | msg "installing copyparty" 43 | . $ve/bin/activate 44 | pip install copyparty 45 | 46 | deactivate 47 | touch $ve/.ok 48 | ) 49 | 50 | ## 51 | ## add copyparty alias to bashrc 52 | 53 | grep -qE '^alias copyparty=' ~/.bashrc 2>/dev/null || { 54 | msg "adding alias to bashrc" 55 | echo "alias copyparty='$HOME/copyparty-android.sh'" >> ~/.bashrc 56 | } 57 | 58 | ## 59 | ## start copyparty 60 | 61 | imsg "starting copyparty" 62 | $ve/bin/python -m copyparty "$@" 63 | -------------------------------------------------------------------------------- /scripts/deps-docker/Makefile: -------------------------------------------------------------------------------- 1 | self := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | vend := $(self)/../../copyparty/web/deps 3 | 4 | # prefers podman-docker (optionally rootless) over actual docker/moby 5 | 6 | all: 7 | cp -pv ../uncomment.py . 8 | 9 | docker build -t build-copyparty-deps . 10 | 11 | rm -rf $(vend) 12 | mkdir $(vend) 13 | 14 | echo "tar -cC /z dist" | \ 15 | docker run --rm -i build-copyparty-deps:latest | \ 16 | tar -xvC $(vend) --strip-components=1 17 | 18 | touch $(vend)/__init__.py 19 | chown -R `stat $(self) -c %u:%g` $(vend) 20 | 21 | purge: 22 | -docker kill `docker ps -q` 23 | -docker rm `docker ps -qa` 24 | -docker rmi `docker images -qa` 25 | 26 | sh: 27 | @printf "\n\033[1;31mopening a shell in the most recently created docker image\033[0m\n" 28 | docker run --rm -it `docker images -aq | head -n 1` /bin/ash 29 | -------------------------------------------------------------------------------- /scripts/deps-docker/busy-mp3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cat >/dev/null <s0a.pcm 35 | tail -c +$ofs2 s0.pcm >s0b.pcm 36 | tail -c +$ofs3 s0.pcm >s0c.pcm 37 | cat s{0a,1,0,0b,1,0c}.pcm > a.pcm 38 | lame --silent -r -s 48 --bitwidth 16 --signed a.pcm -m j --resample 48 -b $kbps -q 0 $nores $f.mp3 39 | if [ $fast ] 40 | then gzip -c9 <$f.mp3 >$f.mp3.gz 41 | else pigz -c11 -I1 <$f.mp3 >$f.mp3.gz 42 | fi 43 | sz=$(wc -c <$f.mp3.gz) 44 | printf '\033[A%d %s\033[K\n' $sz $f 45 | [ $sz -le $((min+10)) ] && echo 46 | [ $sz -le $min ] && echo && min=$sz 47 | 48 | done;done;done;done;done;done;done;done;done;done 49 | true 50 | 51 | f=a.b32--nores-f425-v0-h1124-1042-o9214-0-0.mp3 52 | [ $fast ] && 53 | pigz -c11 -I1 <$f >busy.mp3.gz || 54 | mv $f.gz busy.mp3.gz 55 | 56 | sz=$(wc -c ", "lt": "<" 10 | +} 11 | -------------------------------------------------------------------------------- /scripts/deps-docker/mini-fa.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | that was the original copyright ^ 4 | now here's a tiny subset of fontawesome 5 | */ 6 | 7 | @font-face { 8 | font-family: 'fa'; 9 | font-style: normal; 10 | font-weight: 400; 11 | font-display: block; 12 | src: url("mini-fa.woff") format("woff"); 13 | } 14 | 15 | .fa, 16 | .fas, 17 | .far, 18 | .fal, 19 | .fad, 20 | .fab { 21 | -moz-osx-font-smoothing: grayscale; 22 | -webkit-font-smoothing: antialiased; 23 | display: inline-block; 24 | font-style: normal; 25 | font-variant: normal; 26 | text-rendering: auto; 27 | line-height: 1; 28 | font-family: 'fa'; 29 | font-weight: 400; 30 | } 31 | 32 | :add 33 | arrows-alt 34 | bold 35 | code 36 | columns 37 | eraser 38 | eye 39 | heading 40 | image 41 | italic 42 | lightbulb 43 | link 44 | list-ol 45 | list-ul 46 | minus 47 | question-circle 48 | quote-left 49 | redo 50 | save 51 | strikethrough 52 | table 53 | undo -------------------------------------------------------------------------------- /scripts/deps-docker/mini-fa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | set -e 3 | 4 | orig_css="$(find /z/fontawesome-fre* -name fontawesome.css | head -n 1)" 5 | orig_woff="$(find /z/fontawesome-fre* -name fa-solid-900.woff | head -n 1)" 6 | 7 | # first grab the copyright meme 8 | awk '1; / *\*\// {exit}' <"$orig_css" >/z/dist/mini-fa.css 9 | 10 | # then add the static part of our css template 11 | awk '/^:add/ {exit} 1' >/z/dist/mini-fa.css 12 | 13 | # then take the list of icons to include 14 | awk 'o; /^:add/ {o=1}' >/z/dist/mini-fa.css 19 | 20 | # expecting this input btw: 21 | # .fa-python:before { 22 | # content: "\f3e2"; } 23 | 24 | # get the codepoints (should produce lines like "f3e2") 25 | awk '/:before .content:"\\/ {sub(/[^"]+"./,""); sub(/".*/,""); print}' /z/icon.list 26 | 27 | # and finally create a woff with just our icons 28 | pyftsubset "$orig_woff" --unicodes-file=/z/icon.list --no-ignore-missing-unicodes --flavor=woff --with-zopfli --output-file=/z/dist/no-pk/mini-fa.woff --verbose 29 | 30 | # scp is easier, just want basic latin 31 | pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose 32 | 33 | exit 0 34 | 35 | # kinda works but ruins hinting on windows, just use the old version of the font which has correct baseline 36 | python3 shiftbase.py /z/dist/no-pk/scp.woff2 37 | cd /z/dist/no-pk/ 38 | mv scp.woff2.woff2 scp.woff2 39 | -------------------------------------------------------------------------------- /scripts/deps-docker/shiftbase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from fontTools.ttLib import TTFont, newTable 5 | 6 | 7 | def main(): 8 | woff = sys.argv[1] 9 | font = TTFont(woff) 10 | print(repr(font["hhea"].__dict__)) 11 | print(repr(font["OS/2"].__dict__)) 12 | # font["hhea"].ascent = round(base_asc * mul) 13 | # font["hhea"].descent = round(base_desc * mul) 14 | # font["OS/2"].usWinAscent = round(base_asc * mul) 15 | font["OS/2"].usWinDescent = round(font["OS/2"].usWinDescent * 1.1) 16 | font["OS/2"].sTypoDescender = round(font["OS/2"].sTypoDescender * 1.1) 17 | 18 | try: 19 | del font["post"].mapping["Delta#1"] 20 | except: 21 | pass 22 | 23 | font.save(woff + ".woff2") 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | -------------------------------------------------------------------------------- /scripts/deps-docker/zopfli.makefile: -------------------------------------------------------------------------------- 1 | all: $(addsuffix .gz, $(wildcard *.js *.css)) 2 | 3 | %.gz: % 4 | pigz -11 -I 2048 $< 5 | 6 | # pigz -11 -J 34 -I 100 -F < $< > $@.first 7 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.ac: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-ac" \ 7 | org.opencontainers.image.description="copyparty with Pillow and FFmpeg (image/audio/video thumbnails, audio transcoding, media tags)" 8 | ENV XDG_CONFIG_HOME=/cfg 9 | 10 | RUN apk --no-cache add !pyc \ 11 | tzdata wget mimalloc2 mimalloc2-insecure \ 12 | py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \ 13 | ffmpeg 14 | 15 | COPY i/dist/copyparty-sfx.py innvikler.sh ./ 16 | ADD base ./base 17 | RUN ash innvikler.sh ac 18 | 19 | WORKDIR /w 20 | EXPOSE 3923 21 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] 22 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.dj: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-dj" \ 7 | org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection" 8 | ENV XDG_CONFIG_HOME=/cfg 9 | 10 | COPY i/bin/mtag/install-deps.sh ./ 11 | COPY i/bin/mtag/audio-bpm.py /mtag/ 12 | COPY i/bin/mtag/audio-key.py /mtag/ 13 | RUN apk add -U !pyc \ 14 | tzdata wget mimalloc2 mimalloc2-insecure \ 15 | py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \ 16 | py3-pip py3-cffi \ 17 | ffmpeg \ 18 | vips-jxl vips-heif vips-poppler vips-magick \ 19 | py3-numpy fftw libsndfile \ 20 | vamp-sdk vamp-sdk-libs \ 21 | && apk add -t .bd \ 22 | bash wget gcc g++ make cmake patchelf \ 23 | python3-dev ffmpeg-dev fftw-dev libsndfile-dev \ 24 | py3-wheel py3-numpy-dev libffi-dev \ 25 | vamp-sdk-dev \ 26 | && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ 27 | && python3 -m pip install pyvips \ 28 | && bash install-deps.sh \ 29 | && apk del py3-pip .bd \ 30 | && chmod 777 /root \ 31 | && ln -s /root/vamp /root/.local / 32 | 33 | COPY i/dist/copyparty-sfx.py innvikler.sh ./ 34 | ADD base ./base 35 | RUN ash innvikler.sh dj 36 | 37 | WORKDIR /w 38 | EXPOSE 3923 39 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] 40 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.im: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-im" \ 7 | org.opencontainers.image.description="copyparty with Pillow and Mutagen (image thumbnails, media tags)" 8 | ENV XDG_CONFIG_HOME=/cfg 9 | 10 | RUN apk --no-cache add !pyc \ 11 | tzdata wget mimalloc2 mimalloc2-insecure \ 12 | py3-jinja2 py3-argon2-cffi py3-pillow py3-mutagen 13 | 14 | COPY i/dist/copyparty-sfx.py innvikler.sh ./ 15 | ADD base ./base 16 | RUN ash innvikler.sh im 17 | 18 | WORKDIR /w 19 | EXPOSE 3923 20 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] 21 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.iv: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-iv" \ 7 | org.opencontainers.image.description="copyparty with Pillow, FFmpeg, libvips (image/audio/video thumbnails, audio transcoding, media tags)" 8 | ENV XDG_CONFIG_HOME=/cfg 9 | 10 | RUN apk add -U !pyc \ 11 | tzdata wget mimalloc2 mimalloc2-insecure \ 12 | py3-jinja2 py3-argon2-cffi py3-pyzmq py3-pillow \ 13 | py3-pip py3-cffi \ 14 | ffmpeg \ 15 | vips-jxl vips-heif vips-poppler vips-magick \ 16 | && apk add -t .bd \ 17 | bash wget gcc g++ make cmake patchelf \ 18 | python3-dev py3-wheel libffi-dev \ 19 | && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ 20 | && python3 -m pip install pyvips \ 21 | && apk del py3-pip .bd 22 | 23 | COPY i/dist/copyparty-sfx.py innvikler.sh ./ 24 | ADD base ./base 25 | RUN ash innvikler.sh iv 26 | 27 | WORKDIR /w 28 | EXPOSE 3923 29 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] 30 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.min: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-min" \ 7 | org.opencontainers.image.description="just copyparty, no thumbnails / media tags / audio transcoding" 8 | ENV XDG_CONFIG_HOME=/cfg 9 | 10 | RUN apk --no-cache add !pyc \ 11 | py3-jinja2 12 | 13 | COPY i/dist/copyparty-sfx.py innvikler.sh ./ 14 | RUN ash innvikler.sh min 15 | 16 | WORKDIR /w 17 | EXPOSE 3923 18 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "--no-thumb", "-c", "/z/initcfg"] 19 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.min.pip: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ 4 | org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ 5 | org.opencontainers.image.licenses="MIT" \ 6 | org.opencontainers.image.title="copyparty-min-pip" \ 7 | org.opencontainers.image.description="just copyparty, no thumbnails, no media tags, no audio transcoding" 8 | ENV PYTHONPYCACHEPREFIX=/tmp/pyc \ 9 | XDG_CONFIG_HOME=/cfg 10 | 11 | RUN apk --no-cache add python3 py3-pip !pyc \ 12 | && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ 13 | && python3 -m pip install copyparty \ 14 | && apk del py3-pip \ 15 | && rm -rf /tmp/pyc \ 16 | && mkdir /cfg /w \ 17 | && chmod 777 /cfg /w \ 18 | && echo % /cfg > initcfg 19 | 20 | WORKDIR /w 21 | EXPOSE 3923 22 | ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "--no-thumb", "-c", "/z/initcfg"] 23 | -------------------------------------------------------------------------------- /scripts/docker/base/Dockerfile.zlibng: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | WORKDIR /z 3 | 4 | RUN apk add py3-pip make gcc musl-dev python3-dev 5 | RUN pip wheel https://files.pythonhosted.org/packages/c4/a7/0b7673be5945071e99364a3ac1987b02fc1d416617e97f3e8816d275174e/zlib_ng-0.5.1.tar.gz 6 | -------------------------------------------------------------------------------- /scripts/docker/base/Makefile: -------------------------------------------------------------------------------- 1 | self := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | all: 4 | # build zlib-ng from source so we know how the sausage was made 5 | # (still only doing the archs which are officially supported/tested) 6 | 7 | podman build --arch amd64 -t localhost/cpp-zlibng-amd64:latest -f Dockerfile.zlibng . 8 | podman run --arch amd64 --rm --log-driver=none -i localhost/cpp-zlibng-amd64:latest tar -cC/z . | tar -xv 9 | 10 | podman build --arch arm64 -t localhost/cpp-zlibng-amd64:latest -f Dockerfile.zlibng . 11 | podman run --arch arm64 --rm --log-driver=none -i localhost/cpp-zlibng-amd64:latest tar -cC/z . | tar -xv 12 | 13 | sh: 14 | @printf "\n\033[1;31mopening a shell in the most recently created docker image\033[0m\n" 15 | docker run --rm -it --entrypoint /bin/ash `docker images -aq | head -n 1` 16 | -------------------------------------------------------------------------------- /scripts/docker/devnotes.md: -------------------------------------------------------------------------------- 1 | # building the images yourself 2 | 3 | ```bash 4 | ./make.sh hclean pull img push 5 | ``` 6 | will download the latest copyparty-sfx.py from github unless you have [built it from scratch](../../docs/devnotes.md#just-the-sfx) and then build all the images based on that 7 | 8 | deprecated alternative: run `make` to use the makefile however that uses docker instead of podman and only builds x86_64 9 | 10 | `make.sh` is necessarily(?) overengineered because: 11 | * podman keeps burning dockerhub pulls by not using the cached images (`--pull=never` does not apply to manifests) 12 | * podman cannot build from a local manifest, only local images or remote manifests 13 | 14 | but I don't really know what i'm doing here 💩 15 | 16 | * auth for pushing images to repos; 17 | `podman login docker.io` 18 | `podman login ghcr.io -u 9001` 19 | [about gchq](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) (takes a classic token as password) 20 | 21 | 22 | ## building on alpine 23 | 24 | ```bash 25 | apk add podman{,-docker} 26 | rc-update add cgroups 27 | service cgroups start 28 | vim /etc/containers/storage.conf # driver = "btrfs" 29 | modprobe tun 30 | echo ed:100000:65536 >/etc/subuid 31 | echo ed:100000:65536 >/etc/subgid 32 | apk add qemu-openrc qemu-tools qemu-{arm,armeb,aarch64,s390x,ppc64le} 33 | rc-update add qemu-binfmt 34 | service qemu-binfmt start 35 | ``` 36 | -------------------------------------------------------------------------------- /scripts/genlic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re, os, sys, codecs 4 | 5 | outfile = os.path.realpath(sys.argv[1]) 6 | 7 | os.chdir(os.path.dirname(__file__)) 8 | 9 | with open("../docs/lics.txt", "rb") as f: 10 | s = f.read().decode("utf-8").rstrip("\n") + "\n\n\n\n" 11 | s = re.sub("\nC: ", "\nCopyright (c) ", s) 12 | s = re.sub("\nL: ", "\nLicense: ", s) 13 | ret = s.split("\n") 14 | 15 | lics = [ 16 | "MIT License", 17 | "BSD 2-Clause License", 18 | "BSD 3-Clause License", 19 | "SIL Open Font License v1.1", 20 | ] 21 | 22 | for n, lic in enumerate(lics, 1): 23 | with open("lics/%d.r13" % (n,), "rb") as f: 24 | s = f.read().decode("utf-8") 25 | s = codecs.decode(s, "rot_13") 26 | s = "\n--- %s ---\n\n%s" % (lic, s) 27 | ret.extend(s.split("\n")) 28 | 29 | for n, ln in enumerate(ret): 30 | if not ln.startswith("--- "): 31 | continue 32 | pad = " " * ((80 - len(ln)) // 2) 33 | ln = "%s\033[07m%s\033[0m" % (pad, ln) 34 | ret[n] = ln 35 | 36 | ret.append("") 37 | ret.append("") 38 | 39 | with open(outfile, "wb") as f: 40 | f.write(("\n".join(ret)).encode("utf-8")) 41 | -------------------------------------------------------------------------------- /scripts/help2txt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ( xsel -ob | sed -r ' 5 | s`/home/ed/`~/`; 6 | s/uuid:[0-9a-f-]{36}/autogenerated/; 7 | s/(-salt SALT.*default: )[0-9a-zA-Z/+]{24}\)/\124-character-autogenerated)/; 8 | s/(-salt SALT.*default: )[0-9a-zA-Z/+]{40}\)/\140-character-autogenerated)/; 9 | s/(--name TXT.*default: )[^)]+/\1hostname/; 10 | s/(--hash-mt CORES.*default: )[0-9]+/\1numCores if 5 or less/; 11 | s/(--mtag-mt|th-mt)( CORES.*default: )[0-9]+/\1\2numCores/; 12 | s/(--th-ram-max GB.*default: )[0-9\.]+/\1dynamic/; 13 | ' | awk ' 14 | /^copyparty/{a=1} !a{next} 15 | /^0{20}/{b=1} b&&/^copyparty v[0-9]+\./{s=3} 16 | s{s-=1;next} 1' | 17 | head -n-6; echo eof ) >helptext.txt 18 | exit 0 19 | 20 | 21 | # ===================================================================== 22 | # end of script; below is the explanation how to use this: 23 | 24 | 25 | # first open an infinitely wide console (this is why you own an ultrawide) and copypaste this into it: 26 | for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -pwhash -zm; do 27 | ./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0255d\n\n\n'; done 28 | 29 | # then copypaste all of the output by pressing ctrl-shift-a, ctrl-shift-c 30 | # and finally actually run this script which should produce helptext.txt 31 | -------------------------------------------------------------------------------- /scripts/install-githooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | [ -e setup.py ] || .. 5 | [ -e setup.py ] || { 6 | echo u wot 7 | exit 1 8 | } 9 | 10 | cd .git/hooks 11 | rm -f pre-commit 12 | ln -s ../../scripts/run-tests.sh pre-commit 13 | -------------------------------------------------------------------------------- /scripts/lics/1.r13: -------------------------------------------------------------------------------- 1 | Crezvffvba vf urerol tenagrq, serr bs punetr, gb nal crefba bognvavat n pbcl bs guvf fbsgjner naq nffbpvngrq qbphzragngvba svyrf (gur "Fbsgjner"), gb qrny va gur Fbsgjner jvgubhg erfgevpgvba, vapyhqvat jvgubhg yvzvgngvba gur evtugf gb hfr, pbcl, zbqvsl, zretr, choyvfu, qvfgevohgr, fhoyvprafr, naq/be fryy pbcvrf bs gur Fbsgjner, naq gb crezvg crefbaf gb jubz gur Fbsgjner vf sheavfurq gb qb fb, fhowrpg gb gur sbyybjvat pbaqvgvbaf: 2 | 3 | Gur nobir pbclevtug abgvpr naq guvf crezvffvba abgvpr funyy or vapyhqrq va nyy pbcvrf be fhofgnagvny cbegvbaf bs gur Fbsgjner. 4 | 5 | GUR FBSGJNER VF CEBIVQRQ "NF VF", JVGUBHG JNEENAGL BS NAL XVAQ, RKCERFF BE VZCYVRQ, VAPYHQVAT OHG ABG YVZVGRQ GB GUR JNEENAGVRF BS ZREPUNAGNOVYVGL, SVGARFF SBE N CNEGVPHYNE CHECBFR NAQ ABAVASEVATRZRAG. VA AB RIRAG FUNYY GUR NHGUBEF BE PBCLEVTUG UBYQREF OR YVNOYR SBE NAL PYNVZ, QNZNTRF BE BGURE YVNOVYVGL, JURGURE VA NA NPGVBA BS PBAGENPG, GBEG BE BGUREJVFR, NEVFVAT SEBZ, BHG BS BE VA PBAARPGVBA JVGU GUR FBSGJNER BE GUR HFR BE BGURE QRNYVATF VA GUR FBSGJNER. -------------------------------------------------------------------------------- /scripts/lics/2.r13: -------------------------------------------------------------------------------- 1 | Erqvfgevohgvba naq hfr va fbhepr naq ovanel sbezf, jvgu be jvgubhg zbqvsvpngvba, ner crezvggrq cebivqrq gung gur sbyybjvat pbaqvgvbaf ner zrg: 2 | 3 | 1. Erqvfgevohgvbaf bs fbhepr pbqr zhfg ergnva gur nobir pbclevtug abgvpr, guvf yvfg bs pbaqvgvbaf naq gur sbyybjvat qvfpynvzre. 4 | 5 | 2. Erqvfgevohgvbaf va ovanel sbez zhfg ercebqhpr gur nobir pbclevtug abgvpr, guvf yvfg bs pbaqvgvbaf naq gur sbyybjvat qvfpynvzre va gur qbphzragngvba naq/be bgure zngrevnyf cebivqrq jvgu gur qvfgevohgvba. 6 | 7 | GUVF FBSGJNER VF CEBIVQRQ OL GUR PBCLEVTUG UBYQREF NAQ PBAGEVOHGBEF "NF VF" NAQ NAL RKCERFF BE VZCYVRQ JNEENAGVRF, VAPYHQVAT, OHG ABG YVZVGRQ GB, GUR VZCYVRQ JNEENAGVRF BS ZREPUNAGNOVYVGL NAQ SVGARFF SBE N CNEGVPHYNE CHECBFR NER QVFPYNVZRQ. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE BE PBAGEVOHGBEF OR YVNOYR SBE NAL QVERPG, VAQVERPG, VAPVQRAGNY, FCRPVNY, RKRZCYNEL, BE PBAFRDHRAGVNY QNZNTRF (VAPYHQVAT, OHG ABG YVZVGRQ GB, CEBPHERZRAG BS FHOFGVGHGR TBBQF BE FREIVPRF; YBFF BS HFR, QNGN, BE CEBSVGF; BE OHFVARFF VAGREEHCGVBA) UBJRIRE PNHFRQ NAQ BA NAL GURBEL BS YVNOVYVGL, JURGURE VA PBAGENPG, FGEVPG YVNOVYVGL, BE GBEG (VAPYHQVAT ARTYVTRAPR BE BGUREJVFR) NEVFVAT VA NAL JNL BHG BS GUR HFR BS GUVF FBSGJNER, RIRA VS NQIVFRQ BS GUR CBFFVOVYVGL BS FHPU QNZNTR. -------------------------------------------------------------------------------- /scripts/lics/3.r13: -------------------------------------------------------------------------------- 1 | Erqvfgevohgvba naq hfr va fbhepr naq ovanel sbezf, jvgu be jvgubhg zbqvsvpngvba, ner crezvggrq cebivqrq gung gur sbyybjvat pbaqvgvbaf ner zrg: 2 | 3 | 1. Erqvfgevohgvbaf bs fbhepr pbqr zhfg ergnva gur nobir pbclevtug abgvpr, guvf yvfg bs pbaqvgvbaf naq gur sbyybjvat qvfpynvzre. 4 | 5 | 2. Erqvfgevohgvbaf va ovanel sbez zhfg ercebqhpr gur nobir pbclevtug abgvpr, guvf yvfg bs pbaqvgvbaf naq gur sbyybjvat qvfpynvzre va gur qbphzragngvba naq/be bgure zngrevnyf cebivqrq jvgu gur qvfgevohgvba. 6 | 7 | 3. Arvgure gur anzr bs gur pbclevtug ubyqre abe gur anzrf bs vgf pbagevohgbef znl or hfrq gb raqbefr be cebzbgr cebqhpgf qrevirq sebz guvf fbsgjner jvgubhg fcrpvsvp cevbe jevggra crezvffvba. 8 | 9 | GUVF FBSGJNER VF CEBIVQRQ OL GUR PBCLEVTUG UBYQREF NAQ PBAGEVOHGBEF "NF VF" NAQ NAL RKCERFF BE VZCYVRQ JNEENAGVRF, VAPYHQVAT, OHG ABG YVZVGRQ GB, GUR VZCYVRQ JNEENAGVRF BS ZREPUNAGNOVYVGL NAQ SVGARFF SBE N CNEGVPHYNE CHECBFR NER QVFPYNVZRQ. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE BE PBAGEVOHGBEF OR YVNOYR SBE NAL QVERPG, VAQVERPG, VAPVQRAGNY, FCRPVNY, RKRZCYNEL, BE PBAFRDHRAGVNY QNZNTRF (VAPYHQVAT, OHG ABG YVZVGRQ GB, CEBPHERZRAG BS FHOFGVGHGR TBBQF BE FREIVPRF; YBFF BS HFR, QNGN, BE CEBSVGF; BE OHFVARFF VAGREEHCGVBA) UBJRIRE PNHFRQ NAQ BA NAL GURBEL BS YVNOVYVGL, JURGURE VA PBAGENPG, FGEVPG YVNOVYVGL, BE GBEG (VAPYHQVAT ARTYVTRAPR BE BGUREJVFR) NEVFVAT VA NAL JNL BHG BS GUR HFR BS GUVF FBSGJNER, RIRA VS NQIVFRQ BS GUR CBFFVOVYVGL BS FHPU QNZNTR. -------------------------------------------------------------------------------- /scripts/lics/README.md: -------------------------------------------------------------------------------- 1 | these are foss licenses in rot13 so scanners don't think copyparty isn't mit 2 | 3 | 1=mit 2=2bsd 3=3bsd 4=ofl 4 | -------------------------------------------------------------------------------- /scripts/lics/rot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, codecs 4 | 5 | for fn in os.listdir("."): 6 | if not fn.endswith(".txt"): 7 | continue 8 | with open(fn, "rb") as f: 9 | s = f.read().decode("utf-8") 10 | b = codecs.encode(s, "rot_13").encode("utf-8") 11 | with open(fn.replace("txt", "r13"), "wb") as f: 12 | f.write(b) 13 | -------------------------------------------------------------------------------- /scripts/make-pyz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | echo 4 | 5 | # port install gnutar gsed coreutils 6 | gtar=$(command -v gtar || command -v gnutar) || true 7 | [ ! -z "$gtar" ] && command -v gsed >/dev/null && { 8 | tar() { $gtar "$@"; } 9 | sed() { gsed "$@"; } 10 | command -v grealpath >/dev/null && 11 | realpath() { grealpath "$@"; } 12 | } 13 | 14 | tmv() { 15 | touch -r "$1" t 16 | mv t "$1" 17 | } 18 | ised() { 19 | sed -r "$1" <"$2" >t 20 | tmv "$2" 21 | } 22 | 23 | targs=(--owner=1000 --group=1000) 24 | [ "$OSTYPE" = msys ] && 25 | targs=() 26 | 27 | [ -e copyparty/__main__.py ] || cd .. 28 | [ -e copyparty/__main__.py ] || { 29 | echo "run me from within the project root folder" 30 | echo 31 | exit 1 32 | } 33 | 34 | [ -e sfx/copyparty/__main__.py ] || { 35 | echo "run ./scripts/make-sfx.py first" 36 | echo 37 | exit 1 38 | } 39 | 40 | rm -rf pyz 41 | mkdir -p pyz 42 | cd pyz 43 | 44 | cp -pR ../sfx/{copyparty,partftpy} . 45 | cp -pR ../sfx/{ftp,j2}/* . 46 | 47 | true && { 48 | rm -rf copyparty/web/mde.* copyparty/web/deps/easymde* 49 | echo h > copyparty/web/mde.html 50 | ised '/edit2">edit \(fancy/d' copyparty/web/md.html 51 | } 52 | 53 | ts=$(date -u +%s) 54 | hts=$(date -u +%Y-%m%d-%H%M%S) 55 | ver="$(cat ../sfx/ver)" 56 | 57 | mkdir -p ../dist 58 | pyz_out=../dist/copyparty.pyz 59 | 60 | echo creating loader 61 | sed -r 's/^(VER = ).*/\1"'"$ver"'"/; s/^(STAMP = ).*/\1'$(date +%s)/ \ 62 | <../scripts/ziploader.py \ 63 | >__main__.py 64 | 65 | echo creating pyz 66 | rm -f $pyz_out 67 | zip -9 -q -r $pyz_out * 68 | 69 | echo done: 70 | echo " $(realpath $pyz_out)" 71 | -------------------------------------------------------------------------------- /scripts/prep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # general housekeeping before a release 5 | 6 | self=$(cd -- "$(dirname "$BASH_SOURCE")"; pwd -P) 7 | ver=$(awk '/^VERSION/{gsub(/[^0-9]/," ");printf "%d.%d.%d\n",$1,$2,$3}' copyparty/__version__.py) 8 | 9 | update_arch_pkgbuild() { 10 | cd "$self/../contrib/package/arch" 11 | rm -rf x 12 | mkdir x 13 | 14 | sha=$(sha256sum "$self/../dist/copyparty-$ver.tar.gz" | awk '{print$1}') 15 | 16 | awk -v ver=$ver -v sha=$sha ' 17 | /^pkgver=/{sub(/[0-9\.]+/,ver)}; 18 | /^sha256sums=/{sub(/[0-9a-f]{64}/,sha)}; 19 | 1' PKGBUILD >a 20 | mv a PKGBUILD 21 | 22 | rm -rf x 23 | } 24 | 25 | update_nixos_pin() { 26 | ( cd $self/../contrib/package/nix/copyparty; 27 | ./update.py $self/../dist/copyparty-sfx.py ) 28 | } 29 | 30 | update_arch_pkgbuild 31 | update_nixos_pin 32 | -------------------------------------------------------------------------------- /scripts/profile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | sys.path.insert(0, ".") 6 | cmd = sys.argv[1] 7 | 8 | if cmd == "cpp": 9 | from copyparty.__main__ import main 10 | 11 | argv = ["__main__", "-vsrv::r:c,e2ds,e2ts"] 12 | main(argv=argv) 13 | 14 | elif cmd == "test": 15 | from unittest import main 16 | 17 | argv = ["__main__", "discover", "-s", "tests"] 18 | main(module=None, argv=argv) 19 | 20 | else: 21 | raise Exception() 22 | 23 | # import dis; print(dis.dis(main)) 24 | 25 | 26 | # macos: 27 | # option1) python3.9 -m pip install --user -U vmprof==0.4.9 28 | # option2) python3.9 -m pip install --user -U https://github.com/vmprof/vmprof-python/archive/refs/heads/master.zip 29 | # 30 | # python -m vmprof -o prof --lines ./scripts/profile.py test 31 | 32 | # linux: ~/.local/bin/vmprofshow prof tree | awk '$2>1{n=5} !n{next} 1;{n--} !n{print""}' 33 | # macos: ~/Library/Python/3.9/bin/vmprofshow prof tree 34 | # win: %appdata%\..\Roaming\Python\Python39\Scripts\vmprofshow.exe prof tree 35 | -------------------------------------------------------------------------------- /scripts/py2/queue/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function, unicode_literals 3 | 4 | from Queue import Queue, LifoQueue, PriorityQueue, Empty, Full 5 | -------------------------------------------------------------------------------- /scripts/pyinstaller/README.md: -------------------------------------------------------------------------------- 1 | builds copyparty32.exe, fully standalone, compatible with 32bit win7-sp1 and later 2 | 3 | requires a win7 vm which has never been connected to the internet and a host-only network with the linux host at 192.168.123.1 4 | 5 | copyparty.exe is built by a win10-ltsc-2021 vm with similar setup 6 | 7 | first-time setup steps in notes.txt 8 | 9 | run build.sh in the vm to fetch src + compile + push a new exe to the linux host for manual publishing 10 | 11 | 12 | ## ffmpeg 13 | 14 | built with [ffmpeg-windows-build-helpers](https://github.com/rdp/ffmpeg-windows-build-helpers) and [this patch](./ffmpeg.patch) using [these steps](./ffmpeg.txt) 15 | -------------------------------------------------------------------------------- /scripts/pyinstaller/depchk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | e=0 4 | 5 | cd ~/dev/pyi 6 | 7 | ckpypi() { 8 | deps=( 9 | altgraph 10 | pefile 11 | pyinstaller 12 | pyinstaller-hooks-contrib 13 | pywin32-ctypes 14 | Jinja2 15 | MarkupSafe 16 | mutagen 17 | Pillow 18 | ) 19 | for dep in "${deps[@]}"; do 20 | k= 21 | echo -n . 22 | curl -s https://pypi.org/pypi/$dep/json >h 23 | ver=$(jq master, origin/master, origin/HEAD) 5 | # Merge: b0bd70c 9905dd7 6 | # Author: Roger Pack 7 | # Date: Fri Aug 19 23:36:35 2022 -0600 8 | 9 | cd ffmpeg-windows-build-helpers/ 10 | vim cross_compile_ffmpeg.sh 11 | (cd ./sandbox/win32/ffmpeg_git_xp_compat_lgpl/ ; git reset --hard ; git clean -fx ) 12 | ./cross_compile_ffmpeg.sh 13 | for f in sandbox/win32/ffmpeg_git_xp_compat_lgpl/ff{mpeg,probe}.exe; do upx --best --ultra-brute -k $f; mv $f ~/dev; done 14 | -------------------------------------------------------------------------------- /scripts/pyinstaller/icon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | genico() { 5 | 6 | # imagemagick png compression is broken, use pillow instead 7 | convert $1 a.bmp 8 | 9 | #convert a.bmp -trim -resize '48x48!' -strip a.png 10 | python3 <<'EOF' 11 | from PIL import Image 12 | i = Image.open('a.bmp') 13 | i = i.crop(i.getbbox()) 14 | i = i.resize((48,48), Image.BICUBIC) 15 | i = Image.alpha_composite(i,i) 16 | i.save('a.png') 17 | EOF 18 | 19 | pngquant --strip --quality 30 a.png 20 | mv a-*.png a.png 21 | 22 | python3 <up2k.rc2 31 | 32 | #python uncomment.py u2c.py 33 | $APPDATA/python/python37/scripts/pyinstaller -y --clean --upx-dir=. up2k.spec 34 | 35 | ./dist/u2c.exe --version 36 | 37 | csum=$(sha512sum uplod.log 40 | cat uplod.log 41 | 42 | grep -q $csum uplod.log && echo upload OK || { 43 | echo UPLOAD FAILED 44 | exit 1 45 | } 46 | -------------------------------------------------------------------------------- /scripts/pyinstaller/up2k.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = None 5 | 6 | 7 | a = Analysis( 8 | ['u2c.py'], 9 | pathex=[], 10 | binaries=[], 11 | datas=[], 12 | hiddenimports=[], 13 | hookspath=[], 14 | hooksconfig={}, 15 | runtime_hooks=[], 16 | excludes=[ 17 | 'bz2', 18 | 'ftplib', 19 | 'getpass', 20 | 'lzma', 21 | 'pickle', 22 | 'platform', 23 | 'selectors', 24 | 'ssl', 25 | 'subprocess', 26 | 'tarfile', 27 | 'tempfile', 28 | 'tracemalloc', 29 | 'typing', 30 | 'zipfile', 31 | 'zlib', 32 | 'email.contentmanager', 33 | 'email.policy', 34 | 'encodings.zlib_codec', 35 | 'encodings.base64_codec', 36 | 'encodings.bz2_codec', 37 | 'encodings.charmap', 38 | 'encodings.hex_codec', 39 | 'encodings.palmos', 40 | 'encodings.punycode', 41 | 'encodings.rot_13', 42 | 'urllib.response', 43 | 'urllib.robotparser', 44 | ], 45 | win_no_prefer_redirects=False, 46 | win_private_assemblies=False, 47 | cipher=block_cipher, 48 | noarchive=False, 49 | ) 50 | 51 | # this is the only change to the autogenerated specfile: 52 | xdll = ["libcrypto-1_1.dll"] 53 | a.binaries = [x for x in a.binaries if x[0] not in xdll] 54 | 55 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 56 | 57 | exe = EXE( 58 | pyz, 59 | a.scripts, 60 | a.binaries, 61 | a.zipfiles, 62 | a.datas, 63 | [], 64 | name='u2c', 65 | debug=False, 66 | bootloader_ignore_signals=False, 67 | strip=False, 68 | upx=True, 69 | upx_exclude=[], 70 | runtime_tmpdir=None, 71 | console=True, 72 | disable_windowed_traceback=False, 73 | argv_emulation=False, 74 | target_arch=None, 75 | codesign_identity=None, 76 | entitlements_file=None, 77 | version='up2k.rc2', 78 | icon=['up2k.ico'], 79 | ) 80 | -------------------------------------------------------------------------------- /scripts/pyinstaller/up2k.spec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # grep '">encodings.cp' C:/Users/ed/dev/copyparty/bin/dist/xref-up2k.html | sed -r 's/.*encodings.cp//;s/<.*//' | sort -n | uniq | tr '\n' , 5 | # grep -i encodings -A1 build/up2k/xref-up2k.html | sed -r 's/.*(Missing|Excluded)Module.*//' | grep moduletype -B1 | grep -v moduletype 6 | 7 | ex=( 8 | ftplib lzma pickle ssl tarfile bz2 zipfile tracemalloc zlib 9 | email.contentmanager email.policy 10 | encodings.{zlib_codec,base64_codec,bz2_codec,charmap,hex_codec,palmos,punycode,rot_13} 11 | ); 12 | cex=(); for a in "${ex[@]}"; do cex+=(--exclude "$a"); done 13 | $APPDATA/python/python37/scripts/pyi-makespec --version-file up2k.rc2 -i up2k.ico -n u2c -c -F u2c.py "${cex[@]}" 14 | -------------------------------------------------------------------------------- /scripts/rls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # if specified, keep the following sfx flags last: gz gzz fast 5 | 6 | parallel=1 7 | 8 | [ -e make-sfx.sh ] || cd scripts 9 | [ -e make-sfx.sh ] && [ -e deps-docker ] || { 10 | echo cd into the scripts folder first 11 | exit 1 12 | } 13 | 14 | v=$1 15 | 16 | [ "$v" = sfx ] || { 17 | printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1 18 | grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1 19 | 20 | git push all 21 | git tag v$v 22 | git push all --tags 23 | 24 | rm -rf ../dist 25 | 26 | ./make-pypi-release.sh u 27 | (cd .. && python3 ./setup.py clean2) 28 | 29 | ./make-tgz-release.sh $v 30 | } 31 | 32 | rm -f ../dist/copyparty-sfx* 33 | shift 34 | ./make-sfx.sh "$@" 35 | f=../dist/copyparty-sfx 36 | [ -e $f.py ] && s= || s=-gz 37 | # TODO: the -gz suffix is gone, can drop all the $s stuff probably 38 | 39 | $f$s.py --version >/dev/null 40 | 41 | while [ "$1" ]; do 42 | case "$1" in 43 | gz*) break;; 44 | fast) break;; 45 | esac 46 | shift 47 | done 48 | 49 | [ $parallel -gt 1 ] && { 50 | printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n' 51 | trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT 52 | touch .sfx-run 53 | min=99999999 54 | for ((a=0; a<$parallel; a++)); do 55 | while [ -e .sfx-run ]; do 56 | CSN=$a ./make-sfx.sh re "$@" 57 | sz=$(wc -c <$f$a$s.py | awk '{print$1}') 58 | [ $sz -ge $min ] && continue 59 | mv $f$a$s.py $f$s.py.$sz 60 | min=$sz 61 | done & 62 | done 63 | read 64 | exit 65 | } 66 | 67 | while true; do 68 | mv $f$s.py $f$s.$(wc -c <$f$s.py | awk '{print$1}').py 69 | ./make-sfx.sh re "$@" 70 | done 71 | 72 | # git tag -d v$v; git push --delete origin v$v 73 | -------------------------------------------------------------------------------- /scripts/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | if uname | grep -iE '^(msys|mingw)'; then 5 | pids=() 6 | 7 | python -m unittest discover -s tests >/dev/null & 8 | pids+=($!) 9 | 10 | python scripts/test/smoketest.py & 11 | pids+=($!) 12 | 13 | for pid in ${pids[@]}; do 14 | wait $pid 15 | done 16 | exit $? 17 | fi 18 | 19 | # osx support 20 | gtar=$(command -v gtar || command -v gnutar) || true 21 | [ ! -z "$gtar" ] && command -v gfind >/dev/null && { 22 | tar() { $gtar "$@"; } 23 | sed() { gsed "$@"; } 24 | find() { gfind "$@"; } 25 | sort() { gsort "$@"; } 26 | command -v grealpath >/dev/null && 27 | realpath() { grealpath "$@"; } 28 | } 29 | 30 | rm -rf unt 31 | mkdir -p unt/srv 32 | cp -pR copyparty tests unt/ 33 | cd unt 34 | 35 | # resolve symlinks 36 | set +x 37 | find -type l | 38 | while IFS= read -r f1; do ( 39 | cd "${f1%/*}" 40 | f1="./${f1##*/}" 41 | f2="$(readlink "$f1")" 42 | [ -e "$f2" ] || f2="../$f2" 43 | [ -e "$f2" ] || { 44 | echo could not resolve "$f1" 45 | exit 1 46 | } 47 | rm "$f1" 48 | cp -p "$f2" "$f1" 49 | ); done 50 | set -x 51 | 52 | python3 ../scripts/strip_hints/a.py 53 | 54 | pids=() 55 | for py in python{2,3}; do 56 | [ "${1:0:6}" = python ] && [ "$1" != $py ] && continue 57 | 58 | PYTHONPATH= 59 | [ $py = python2 ] && PYTHONPATH=../scripts/py2:../sfx/py37:../sfx/j2 60 | export PYTHONPATH 61 | 62 | [ $py = python2 ] && py=$(command -v python2.7 || echo $py) 63 | 64 | nice $py -m unittest discover -s tests >/dev/null & 65 | pids+=($!) 66 | done 67 | 68 | [ "$1" ] || { 69 | python3 ../scripts/test/smoketest.py & 70 | pids+=($!) 71 | } 72 | 73 | for pid in ${pids[@]}; do 74 | wait $pid 75 | done 76 | -------------------------------------------------------------------------------- /scripts/test/ptrav.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | import time 6 | import itertools 7 | import requests 8 | 9 | atlas = ["%", "25", "2e", "2f", ".", "/"] 10 | 11 | 12 | def genlen(ubase, port, ntot, nth, wlen): 13 | n = 0 14 | t0 = time.time() 15 | print("genlen %s nth %s port %s" % (wlen, nth, port)) 16 | rsession = requests.Session() 17 | ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/") 18 | for path in itertools.product(atlas, repeat=wlen): 19 | if "%" not in path: 20 | continue 21 | path = "".join(path) 22 | if ptn.search(path): 23 | continue 24 | n += 1 25 | if n % ntot != nth: 26 | continue 27 | url = ubase % (port, path) 28 | if n % 500 == nth: 29 | spd = n / (time.time() - t0) 30 | print(wlen, n, int(spd), url) 31 | 32 | try: 33 | r = rsession.get(url) 34 | except KeyboardInterrupt: 35 | raise 36 | except: 37 | print("\n[=== RETRY ===]", url) 38 | try: 39 | r = rsession.get(url) 40 | except: 41 | r = rsession.get(url) 42 | 43 | if "fgsfds" in r.text: 44 | with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f: 45 | f.write(url) 46 | raise Exception("HIT! {}".format(url)) 47 | 48 | 49 | def main(): 50 | ubase = sys.argv[1] 51 | port = int(sys.argv[2]) 52 | ntot = int(sys.argv[3]) 53 | nth = int(sys.argv[4]) 54 | for wlen in range(20): 55 | genlen(ubase, port, ntot, nth, wlen) 56 | 57 | 58 | if __name__ == "__main__": 59 | try: 60 | main() 61 | except KeyboardInterrupt: 62 | pass 63 | 64 | """ 65 | python3 -m copyparty -v srv::r -p 3931 -q -j4 66 | nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 0 67 | nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 1 68 | nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 2 69 | nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 0 70 | nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 1 71 | nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 2 72 | (13x slower than /tests/ptrav.py) 73 | """ 74 | -------------------------------------------------------------------------------- /scripts/test/tftp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # PYTHONPATH=.:~/dev/partftpy/ taskset -c 0 python3 -m copyparty -v srv::r -v srv/junk:junk:A --tftp 3969 5 | 6 | get_src=~/dev/copyparty/srv/ro/palette.flac # tftpwa... 7 | get_fp=ro/${get_src##*/} # server url 8 | get_fn=${get_fp##*/} # just filename 9 | 10 | put_src=~/Downloads/102.zip 11 | put_dst=~/dev/copyparty/srv/junk/102.zip 12 | 13 | export PATH="$PATH:$HOME/src/atftp-0.8.0" 14 | 15 | cd /dev/shm 16 | 17 | echo curl get 1428 v4; curl --tftp-blksize 1428 tftp://127.0.0.1:3969/$get_fp | cmp $get_src || exit 1 18 | echo curl get 1428 v6; curl --tftp-blksize 1428 tftp://[::1]:3969/$get_fp | cmp $get_src || exit 1 19 | 20 | echo curl put 1428 v4; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1 21 | echo curl put 1428 v6; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://[::1]:3969/junk/ && cmp $put_src $put_dst || exit 1 22 | 23 | echo atftp get 1428; rm -f $get_fn && atftp --option "blksize 1428" -g -r $get_fp -l $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1 24 | 25 | echo atftp put 1428; rm -f $put_dst && atftp --option "blksize 1428" 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1 26 | 27 | echo tftp-hpa get; rm -f $get_fn && tftp -v -m binary 127.0.0.1 3969 -c get $get_fp && cmp $get_src $get_fn || exit 1 28 | 29 | echo tftp-hpa put; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c put $put_src junk/102.zip && cmp $put_src $put_dst || exit 1 30 | 31 | echo curl get 512; curl tftp://127.0.0.1:3969/$get_fp | cmp $get_src || exit 1 32 | 33 | echo curl put 512; rm -f $put_dst && curl -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1 34 | 35 | echo atftp get 512; rm -f $get_fn && atftp -g -r $get_fp -l $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1 36 | 37 | echo atftp put 512; rm -f $put_dst && atftp 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1 38 | 39 | echo nice 40 | 41 | rm -f $get_fn 42 | -------------------------------------------------------------------------------- /scripts/tlcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # usage: ./scripts/tlcheck.sh eng chi copyparty/web/browser.js 5 | 6 | awk <"$3" -v lang1=\"$1\": -v lang2=\"$2\": ' 7 | /^\t\}/{fa=0;fb=0} 8 | !/":/{next} 9 | $0~lang1{fa=1} 10 | $0~lang2{fb=1} 11 | fa{a[ia++]=$0} 12 | fb{b[ib++]=$0} 13 | END{for (i=0;i"); 36 | c("{"); 37 | c("}"); 38 | c("&"); 39 | c("\\\$"); 40 | c("\\\\"); 41 | print t $0; 42 | } 43 | ' | 44 | sed -r $' 45 | s/\\\\/\033[1;37;41m\\\\\033[0m/g; 46 | s/\$N/\033[1;37;45m$N\033[0m/g; 47 | s/([{}])/\033[34m\\1\033[0m/g; 48 | s/"/\033[44m"\033[0m/g; 49 | s/\'/\033[45m\'\033[0m/g; 50 | s/&/\033[1;43;30m&\033[0m/g; 51 | s/([<>])/\033[30;47m\\1\033[0m/g 52 | ' | 53 | sed -r 's/\t+//' | 54 | less -R 55 | -------------------------------------------------------------------------------- /scripts/toc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | for f in README.md docs/devnotes.md docs/versus.md; do 5 | 6 | cat $f | awk ' 7 | function pr() { 8 | if (!h) {return}; 9 | if (/^ *[*!#|]/||!s) { 10 | printf "%s\n",h; 11 | h=0; 12 | return 13 | }; 14 | if (/.../) { 15 | printf "%s - %s\n",h,$0; 16 | h=0 17 | }; 18 | }; 19 | /```/{o=!o} 20 | o{next} 21 | /^#/{s=1;rs=0;pr()} 22 | /^#* *(nix package)/{rs=1} 23 | /^#* *(themes|install on android|dev env setup|just the sfx|complete release|optional gpl stuff|nixos module|reverse-proxy perf)|```/{s=rs} 24 | /^#/{ 25 | lv=length($1); 26 | sub(/[^ ]+ /,""); 27 | sub(/\[/,""); 28 | sub(/\]\([^)]+\)/,""); 29 | bab=$0; 30 | gsub(/ /,"-",bab); 31 | gsub(/\./,"",bab); 32 | h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab); 33 | next 34 | } 35 | !h{next} 36 | {sub(/ .*/,"");sub(/[:;,]$/,"")} 37 | {pr()} 38 | ' > toc 39 | 40 | grep -E '^#+ *[^ ]+ toc$' -B1000 -A2 <$f >p1 41 | 42 | h2="$(awk '/^#+ *[^ ]+ toc$/{o=1;next} o&&/^#/{print;exit}' <$f)" 43 | 44 | grep -F "$h2" -B2 -A999999 <$f >p2 45 | 46 | (cat p1; grep -F "${h2#* }" -A1000 $f 47 | 48 | rm p1 p2 toc 49 | 50 | done 51 | -------------------------------------------------------------------------------- /scripts/ziploader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | import traceback 6 | 7 | 8 | VER = None 9 | STAMP = None 10 | WINDOWS = sys.platform in ["win32", "msys"] 11 | 12 | 13 | def msg(*a, **ka): 14 | if a: 15 | a = ["[ZIP]", a[0]] + list(a[1:]) 16 | 17 | ka["file"] = sys.stderr 18 | print(*a, **ka) 19 | 20 | 21 | def confirm(rv): 22 | msg() 23 | msg("retcode", rv if rv else traceback.format_exc()) 24 | if WINDOWS: 25 | msg("*** hit enter to exit ***") 26 | try: 27 | input() 28 | except: 29 | pass 30 | 31 | sys.exit(rv or 1) 32 | 33 | 34 | def run(): 35 | from copyparty.__main__ import main as cm 36 | 37 | cm() 38 | 39 | 40 | def main(): 41 | pktime = time.strftime("%Y-%m-%d, %H:%M:%S", time.gmtime(STAMP)) 42 | msg() 43 | msg("build-time:", pktime, "UTC,", STAMP) 44 | msg("python-bin:", sys.executable) 45 | msg() 46 | 47 | try: 48 | run() 49 | except SystemExit as ex: 50 | c = ex.code 51 | if c not in [0, -15]: 52 | confirm(ex.code) 53 | except KeyboardInterrupt: 54 | pass 55 | except: 56 | confirm(0) 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /srv/ceditable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 47 |

48 | 
49 | 
    50 |
      51 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /srv/chat.md: -------------------------------------------------------------------------------- 1 | ## chattyparty 2 | 3 | this file, combined with the [msg-log](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/msg-log.py) hook, turns copyparty into a makeshift instant-messaging / chat service 4 | 5 | name this file `README.md` and run copyparty as such: 6 | ```bash 7 | python copyparty-sfx.py -emp --xm j,bin/hooks/msg-log.py 8 | ``` 9 | 10 | only the stuff below is important; delete everything from this line up 11 | 12 | ```copyparty_post 13 | render2(dom) { 14 | if (/[?&]edit/.test(location)) return; 15 | setTimeout(function() { treectl.goto(); }, 1000); 16 | // if you wanna go to another folder: treectl.goto('foo baz/', true); 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /srv/expand/README.md: -------------------------------------------------------------------------------- 1 | ## text expansion 2 | 3 | enable expansion of placeholder variables in `README.md` and prologue/epilogue files with `--exp` and customize the list of allowed placeholders to expand using `--exp-md` and `--exp-lg` 4 | 5 | | explanation | placeholder | 6 | | -------------------- | -------------------- | 7 | | your ip address | {{self.ip}} | 8 | | your user-agent | {{self.ua}} | 9 | | your username | {{self.uname}} | 10 | | the `Host` you see | {{self.host}} | 11 | | server unix time | {{srv.itime}} | 12 | | server datetime | {{srv.htime}} | 13 | | server name | {{cfg.name}} | 14 | | logout after | {{cfg.logout}} hours | 15 | | vol reindex interval | {{vf.scan}} | 16 | | thumbnail size | {{vf.thsize}} | 17 | | your country | {{hdr.cf_ipcountry}} | 18 | 19 | placeholders starting with... 20 | * `self.` are grabbed from copyparty's internal state; anything in `httpcli.py` is fair game 21 | * `cfg.` are the global server settings 22 | * `vf.` are the volflags of the current volume 23 | * `hdr.` are grabbed from the client headers; any header is supported, just add it (in lowercase) to the allowlist 24 | * `srv.` are processed inside the `_expand` function in httpcli 25 | 26 | for example (bad example), `hdr_cf_ipcountry` maps to the header `CF-IPCountry` (which is generated by cloudflare before the request is passed on to your server / copyparty) 27 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9001/copyparty/2e53f7979a2b6f88dfcf0145224b3f649576a668/tests/__init__.py -------------------------------------------------------------------------------- /tests/res/idp/1.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [accounts] 9 | ua: pa 10 | 11 | [/] 12 | / 13 | accs: 14 | r: ua 15 | 16 | [/vb] 17 | /b 18 | -------------------------------------------------------------------------------- /tests/res/idp/2.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [accounts] 9 | ua: pa 10 | ub: pb 11 | uc: pc 12 | 13 | [groups] 14 | ga: ua, ub 15 | 16 | [/] 17 | / 18 | accs: 19 | r: @ga 20 | 21 | [/vb] 22 | /b 23 | accs: 24 | r: @ga, ua 25 | 26 | [/vc] 27 | /c 28 | accs: 29 | r: @ga, uc 30 | -------------------------------------------------------------------------------- /tests/res/idp/3.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [/vu/${u}] 9 | / 10 | accs: 11 | r: ${u} 12 | 13 | [/vg/${g}] 14 | /b 15 | accs: 16 | r: @${g} 17 | -------------------------------------------------------------------------------- /tests/res/idp/4.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [accounts] 9 | ua: pa 10 | ub: pb 11 | 12 | [/vu/${u}] 13 | /u-${u} 14 | accs: 15 | r: ${u} 16 | 17 | [/vg/${g}1] 18 | /g1-${g} 19 | accs: 20 | r: @${g} 21 | 22 | [/vg/${g}2] 23 | /g2-${g} 24 | accs: 25 | r: @${g}, ua 26 | -------------------------------------------------------------------------------- /tests/res/idp/5.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [/ga] 9 | /ga 10 | accs: 11 | r: @ga 12 | 13 | [/gb] 14 | /gb 15 | accs: 16 | r: @gb 17 | 18 | [/g] 19 | /g 20 | accs: 21 | r: @ga, @gb 22 | -------------------------------------------------------------------------------- /tests/res/idp/6.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [/get/${u}] 9 | /get/${u} 10 | accs: 11 | g: * 12 | r: ${u}, @su 13 | m: @su 14 | 15 | [/priv/${u}] 16 | /priv/${u} 17 | accs: 18 | r: ${u}, @su 19 | m: @su 20 | 21 | [/team/${g}/${u}] 22 | /team/${g}/${u} 23 | accs: 24 | r: @${g} 25 | -------------------------------------------------------------------------------- /tests/res/idp/7.conf: -------------------------------------------------------------------------------- 1 | # -*- mode: yaml -*- 2 | # vim: ft=yaml: 3 | 4 | [global] 5 | idp-h-usr: x-idp-user 6 | idp-h-grp: x-idp-group 7 | 8 | [/u/${u}] 9 | /u/${u} 10 | accs: 11 | r: * 12 | 13 | [/uya/${u%+ga}] 14 | /uya/${u} 15 | accs: 16 | r: * 17 | 18 | [/uyab/${u%+ga,%+gb}] 19 | /uyab/${u} 20 | accs: 21 | r: * 22 | 23 | [/una/${u%-ga}] 24 | /una/${u} 25 | accs: 26 | r: * 27 | 28 | [/unab/${u%-ga,%-gb}] 29 | /unab/${u} 30 | accs: 31 | r: * 32 | 33 | [/gya/${g%+ga}] 34 | /gya/${g} 35 | accs: 36 | r: * 37 | 38 | [/gna/${g%-ga}] 39 | /gna/${g} 40 | accs: 41 | r: * 42 | 43 | [/gnab/${g%-ga,%-gb}] 44 | /gnab/${g} 45 | accs: 46 | r: * 47 | -------------------------------------------------------------------------------- /tests/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import runpy 4 | import sys 5 | 6 | host = sys.argv[1] 7 | sys.argv = sys.argv[:1] + sys.argv[2:] 8 | sys.path.insert(0, ".") 9 | 10 | 11 | def rp(): 12 | runpy.run_module("unittest", run_name="__main__") 13 | 14 | 15 | if host == "vmprof": 16 | rp() 17 | 18 | elif host == "cprofile": 19 | import cProfile 20 | import pstats 21 | 22 | log_fn = "cprofile.log" 23 | cProfile.run("rp()", log_fn) 24 | p = pstats.Stats(log_fn) 25 | p.sort_stats(pstats.SortKey.CUMULATIVE).print_stats(64) 26 | 27 | 28 | """ 29 | python3.9 tests/run.py cprofile -v tests/test_httpcli.py 30 | 31 | python3.9 -m pip install --user vmprof 32 | python3.9 -m vmprof --lines -o vmprof.log tests/run.py vmprof -v tests/test_httpcli.py 33 | """ 34 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | from __future__ import print_function, unicode_literals 4 | 5 | import unittest 6 | 7 | from copyparty.__main__ import PY2 8 | from copyparty.util import w8enc 9 | from tests import util as tu 10 | 11 | 12 | class TestUtils(unittest.TestCase): 13 | def cmp(self, orig, t1, t2): 14 | if t1 != t2: 15 | raise Exception("\n%r\n%r\n%r\n" % (w8enc(orig), t1, t2)) 16 | 17 | def test_quotep(self): 18 | if PY2: 19 | raise unittest.SkipTest() 20 | 21 | from copyparty.util import _quotep3, _quotep3b, w8dec 22 | 23 | txt = w8dec(tu.randbytes(8192)) 24 | self.cmp(txt, _quotep3(txt), _quotep3b(txt)) 25 | 26 | def test_unquote(self): 27 | if PY2: 28 | raise unittest.SkipTest() 29 | 30 | from urllib.parse import unquote_to_bytes as u2b 31 | 32 | from copyparty.util import unquote 33 | 34 | for btxt in ( 35 | tu.randbytes(8192), 36 | br"%ed%91qw,er;ty%20as df?gh+jkl%zxc&vbn \"rty'uio&asd fgh", 37 | ): 38 | self.cmp(btxt, unquote(btxt), u2b(btxt)) 39 | --------------------------------------------------------------------------------