Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39399934ee | ||
|
|
b47635150a | ||
|
|
78d2f69ed5 | ||
|
|
7a98dc669e | ||
|
|
2f15bb5085 | ||
|
|
712a578e6c | ||
|
|
d8dfc4ccb2 | ||
|
|
e413007eb0 | ||
|
|
6d1d3e48d8 | ||
|
|
04966164ce | ||
|
|
8b62aa7cc7 | ||
|
|
1088e8c6a5 | ||
|
|
8c54c2226f | ||
|
|
f74ac1f18b | ||
|
|
25931e62fd | ||
|
|
707a940399 | ||
|
|
87ef50d384 | ||
|
|
dcadf2b11c | ||
|
|
37a690a4c3 | ||
|
|
87ad23fb93 | ||
|
|
5f54d534e3 |
@@ -57,7 +57,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
|
|||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
||||||
* [server config](#server-config) - using arguments or config files, or a mix of both
|
* [server config](#server-config) - using arguments or config files, or a mix of both
|
||||||
* [zeroconf](#zeroconf) - announce enabled services on the LAN
|
* [zeroconf](#zeroconf) - announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png))
|
||||||
* [mdns](#mdns) - LAN domain-name and feature announcer
|
* [mdns](#mdns) - LAN domain-name and feature announcer
|
||||||
* [ssdp](#ssdp) - windows-explorer announcer
|
* [ssdp](#ssdp) - windows-explorer announcer
|
||||||
* [qr-code](#qr-code) - print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/194728533-6f00849b-c6ac-43c6-9359-83e454d11e00.png) for quick access
|
* [qr-code](#qr-code) - print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/194728533-6f00849b-c6ac-43c6-9359-83e454d11e00.png) for quick access
|
||||||
@@ -177,7 +177,7 @@ recommended additional steps on debian which enable audio metadata and thumbnai
|
|||||||
* browser
|
* browser
|
||||||
* ☑ [navpane](#navpane) (directory tree sidebar)
|
* ☑ [navpane](#navpane) (directory tree sidebar)
|
||||||
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
||||||
* ☑ audio player (with OS media controls and opus transcoding)
|
* ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus transcoding)
|
||||||
* ☑ image gallery with webm player
|
* ☑ image gallery with webm player
|
||||||
* ☑ textfile browser with syntax hilighting
|
* ☑ textfile browser with syntax hilighting
|
||||||
* ☑ [thumbnails](#thumbnails)
|
* ☑ [thumbnails](#thumbnails)
|
||||||
@@ -208,7 +208,7 @@ project goals / philosophy
|
|||||||
|
|
||||||
* inverse linux philosophy -- do all the things, and do an *okay* job
|
* inverse linux philosophy -- do all the things, and do an *okay* job
|
||||||
* quick drop-in service to get a lot of features in a pinch
|
* quick drop-in service to get a lot of features in a pinch
|
||||||
* check [the alternatives](./docs/versus.md)
|
* some of [the alternatives](./docs/versus.md) might be a better fit for you
|
||||||
* run anywhere, support everything
|
* run anywhere, support everything
|
||||||
* as many web-browsers and python versions as possible
|
* as many web-browsers and python versions as possible
|
||||||
* every browser should at least be able to browse, download, upload files
|
* every browser should at least be able to browse, download, upload files
|
||||||
@@ -693,7 +693,7 @@ using arguments or config files, or a mix of both:
|
|||||||
|
|
||||||
## zeroconf
|
## zeroconf
|
||||||
|
|
||||||
announce enabled services on the LAN if you specify the `-z` option, which enables [mdns](#mdns) and [ssdp](#ssdp)
|
announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png)) -- `-z` enables both [mdns](#mdns) and [ssdp](#ssdp)
|
||||||
|
|
||||||
* `--z-on` / `--z-off`' limits the feature to certain networks
|
* `--z-on` / `--z-off`' limits the feature to certain networks
|
||||||
|
|
||||||
@@ -1270,6 +1270,7 @@ other misc notes:
|
|||||||
|
|
||||||
* you can disable directory listings by giving permission `g` instead of `r`, only accepting direct URLs to files
|
* you can disable directory listings by giving permission `g` instead of `r`, only accepting direct URLs to files
|
||||||
* combine this with volflag `c,fk` to generate filekeys (per-file accesskeys); users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
|
* combine this with volflag `c,fk` to generate filekeys (per-file accesskeys); users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
|
||||||
|
* the default filekey entropy is fairly small so give `--fk-salt` around 30 characters if you want filekeys longer than 16 chars
|
||||||
* permissions `wG` lets users upload files and receive their own filekeys, still without being able to see other uploads
|
* permissions `wG` lets users upload files and receive their own filekeys, still without being able to see other uploads
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ these programs either take zero arguments, or a filepath (the affected file), or
|
|||||||
|
|
||||||
|
|
||||||
# after upload
|
# after upload
|
||||||
* [notify.py](notify.py) shows a desktop notification
|
* [notify.py](notify.py) shows a desktop notification ([example](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png))
|
||||||
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks
|
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
||||||
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
bin/hooks/discord-announce.py
Normal file → Executable file
0
bin/hooks/discord-announce.py
Normal file → Executable file
44
bin/hooks/notify.py
Normal file → Executable file
44
bin/hooks/notify.py
Normal file → Executable file
@@ -1,20 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess as sp
|
||||||
from plyer import notification
|
from plyer import notification
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
_ = r"""
|
||||||
show os notification on upload; works on windows, linux, macos
|
show os notification on upload; works on windows, linux, macos, android
|
||||||
|
|
||||||
depdencies:
|
depdencies:
|
||||||
python3 -m pip install --user -U plyer
|
windows: python3 -m pip install --user -U plyer
|
||||||
|
linux: python3 -m pip install --user -U plyer
|
||||||
|
macos: python3 -m pip install --user -U plyer pyobjus
|
||||||
|
android: just termux and termux-api
|
||||||
|
|
||||||
example usage as global config:
|
example usages; either as global config (all volumes) or as volflag:
|
||||||
--xau f,bin/hooks/notify.py
|
--xau f,bin/hooks/notify.py
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:c,xau=f,bin/hooks/notify.py
|
-v srv/inc:inc:c,xau=f,bin/hooks/notify.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xau = execute after upload
|
xau = execute after upload
|
||||||
@@ -22,8 +26,36 @@ parameters explained,
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from copyparty.util import humansize
|
||||||
|
except:
|
||||||
|
|
||||||
|
def humansize(n):
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
notification.notify(title="new file uploaded", message=sys.argv[1], timeout=10)
|
fp = sys.argv[1]
|
||||||
|
dp, fn = os.path.split(fp)
|
||||||
|
try:
|
||||||
|
sz = humansize(os.path.getsize(fp))
|
||||||
|
except:
|
||||||
|
sz = "?"
|
||||||
|
|
||||||
|
msg = "{} ({})\n📁 {}".format(fn, sz, dp)
|
||||||
|
title = "File received"
|
||||||
|
|
||||||
|
if "com.termux" in sys.executable:
|
||||||
|
sp.run(["termux-notification", "-t", title, "-c", msg])
|
||||||
|
return
|
||||||
|
|
||||||
|
icon = "emblem-documents-symbolic" if sys.platform == "linux" else ""
|
||||||
|
notification.notify(
|
||||||
|
title=title,
|
||||||
|
message=msg,
|
||||||
|
app_icon=icon,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
0
bin/hooks/reject-extension.py
Normal file → Executable file
0
bin/hooks/reject-extension.py
Normal file → Executable file
0
bin/hooks/reject-mimetype.py
Normal file → Executable file
0
bin/hooks/reject-mimetype.py
Normal file → Executable file
0
bin/hooks/wget.py
Normal file → Executable file
0
bin/hooks/wget.py
Normal file → Executable file
@@ -4,7 +4,7 @@ set -e
|
|||||||
# runs copyparty (or any other program really) in a chroot
|
# runs copyparty (or any other program really) in a chroot
|
||||||
#
|
#
|
||||||
# assumption: these directories, and everything within, are owned by root
|
# assumption: these directories, and everything within, are owned by root
|
||||||
sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr )
|
sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr /etc/alternatives )
|
||||||
|
|
||||||
|
|
||||||
# error-handler
|
# error-handler
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
<!--
|
<!--
|
||||||
|
NOTE: DEPRECATED; please use the javascript version instead:
|
||||||
|
https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/minimal-up2k.js
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
save this as .epilogue.html inside a write-only folder to declutter the UI, makes it look like
|
save this as .epilogue.html inside a write-only folder to declutter the UI, makes it look like
|
||||||
https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png
|
https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png
|
||||||
|
|
||||||
@@ -11,7 +16,7 @@
|
|||||||
|
|
||||||
/* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
|
/* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
|
||||||
|
|
||||||
#ops, #tree, #path, #epi+h2, /* main tabs and navigators (tree/breadcrumbs) */
|
#ops, #tree, #path, #wfp, /* main tabs and navigators (tree/breadcrumbs) */
|
||||||
|
|
||||||
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ almost the same as minimal-up2k.html except this one...:
|
|||||||
var u2min = `
|
var u2min = `
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
#ops, #path, #tree, #files, #epi+div+h2,
|
#ops, #path, #tree, #files, #wfp,
|
||||||
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
|
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -55,5 +55,5 @@ var u2min = `
|
|||||||
if (!has(perms, 'read')) {
|
if (!has(perms, 'read')) {
|
||||||
var e2 = mknod('div');
|
var e2 = mknod('div');
|
||||||
e2.innerHTML = u2min;
|
e2.innerHTML = u2min;
|
||||||
ebi('wrap').insertBefore(e2, QS('#epi+h2'));
|
ebi('wrap').insertBefore(e2, QS('#wfp'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ def get_sects():
|
|||||||
"g" (get): download files, but cannot see folder contents
|
"g" (get): download files, but cannot see folder contents
|
||||||
"G" (upget): "get", but can see filekeys of their own uploads
|
"G" (upget): "get", but can see filekeys of their own uploads
|
||||||
|
|
||||||
too many volflags to list here, see the other sections
|
too many volflags to list here, see --help-flags
|
||||||
|
|
||||||
example:\033[35m
|
example:\033[35m
|
||||||
-a ed:hunter2 -v .::r:rw,ed -v ../inc:dump:w:rw,ed:c,nodupe \033[36m
|
-a ed:hunter2 -v .::r:rw,ed -v ../inc:dump:w:rw,ed:c,nodupe \033[36m
|
||||||
@@ -535,6 +535,8 @@ def get_sects():
|
|||||||
\033[36mxlink$\033[35m cross-volume dupe detection / linking
|
\033[36mxlink$\033[35m cross-volume dupe detection / linking
|
||||||
\033[36mxdev\033[35m do not descend into other filesystems
|
\033[36mxdev\033[35m do not descend into other filesystems
|
||||||
\033[36mxvol\033[35m skip symlinks leaving the volume root
|
\033[36mxvol\033[35m skip symlinks leaving the volume root
|
||||||
|
\033[36mdotsrch\033[35m show dotfiles in search results
|
||||||
|
\033[36mnodotsrch\033[35m hide dotfiles in search results (default)
|
||||||
|
|
||||||
\033[0mdatabase, audio tags:
|
\033[0mdatabase, audio tags:
|
||||||
"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...
|
"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...
|
||||||
@@ -552,6 +554,12 @@ def get_sects():
|
|||||||
\033[36mhtml_head=TXT\033[35m includes TXT in the <head>
|
\033[36mhtml_head=TXT\033[35m includes TXT in the <head>
|
||||||
\033[36mrobots\033[35m allows indexing by search engines (default)
|
\033[36mrobots\033[35m allows indexing by search engines (default)
|
||||||
\033[36mnorobots\033[35m kindly asks search engines to leave
|
\033[36mnorobots\033[35m kindly asks search engines to leave
|
||||||
|
\033[36mno_sb_md\033[35m disable js sandbox for markdown files
|
||||||
|
\033[36mno_sb_lg\033[35m disable js sandbox for prologue/epilogue
|
||||||
|
\033[36msb_md\033[35m enable js sandbox for markdown files (default)
|
||||||
|
\033[36msb_lg\033[35m enable js sandbox for prologue/epilogue (default)
|
||||||
|
\033[36mmd_sbf\033[35m list of markdown-sandbox safeguards to disable
|
||||||
|
\033[36mlg_sbf\033[35m list of *logue-sandbox safeguards to disable
|
||||||
|
|
||||||
\033[0mothers:
|
\033[0mothers:
|
||||||
\033[36mfk=8\033[35m generates per-file accesskeys,
|
\033[36mfk=8\033[35m generates per-file accesskeys,
|
||||||
@@ -808,7 +816,7 @@ def add_smb(ap):
|
|||||||
|
|
||||||
|
|
||||||
def add_hooks(ap):
|
def add_hooks(ap):
|
||||||
ap2 = ap.add_argument_group('hooks (see --help-hooks)')
|
ap2 = ap.add_argument_group('event hooks (see --help-hooks)')
|
||||||
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute CMD before a file upload starts")
|
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute CMD before a file upload starts")
|
||||||
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute CMD after a file upload finishes")
|
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute CMD after a file upload finishes")
|
||||||
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute CMD before a file move/rename")
|
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute CMD before a file move/rename")
|
||||||
@@ -944,6 +952,7 @@ def add_db_general(ap, hcores):
|
|||||||
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10, help="defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...)")
|
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10, help="defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...)")
|
||||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than SEC seconds")
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than SEC seconds")
|
||||||
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
||||||
|
ap2.add_argument("--dotsrch", action="store_true", help="show dotfiles in search results (volflags: dotsrch | nodotsrch)")
|
||||||
|
|
||||||
|
|
||||||
def add_db_metadata(ap):
|
def add_db_metadata(ap):
|
||||||
@@ -979,8 +988,8 @@ def add_ui(ap, retry):
|
|||||||
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
||||||
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
|
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
|
||||||
ap2.add_argument("--pb-url", metavar="URL", type=u, default="https://github.com/9001/copyparty", help="powered-by link; disable with -np")
|
ap2.add_argument("--pb-url", metavar="URL", type=u, default="https://github.com/9001/copyparty", help="powered-by link; disable with -np")
|
||||||
ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms modals popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox")
|
ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox")
|
||||||
ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms modals popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for prologue/epilogue docs (volflag=lg_sbf)")
|
ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for prologue/epilogue docs (volflag=lg_sbf)")
|
||||||
ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README.md documents (volflags: no_sb_md | sb_md)")
|
ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README.md documents (volflags: no_sb_md | sb_md)")
|
||||||
ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
|
ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 6, 1)
|
VERSION = (1, 6, 3)
|
||||||
CODENAME = "cors k"
|
CODENAME = "cors k"
|
||||||
BUILD_DT = (2023, 1, 29)
|
BUILD_DT = (2023, 1, 31)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -1128,6 +1128,7 @@ class AuthSrv(object):
|
|||||||
("no_dedup", "copydupes"),
|
("no_dedup", "copydupes"),
|
||||||
("magic", "magic"),
|
("magic", "magic"),
|
||||||
("xlink", "xlink"),
|
("xlink", "xlink"),
|
||||||
|
("dotsrch", "dotsrch"),
|
||||||
):
|
):
|
||||||
if getattr(self.args, ga):
|
if getattr(self.args, ga):
|
||||||
vol.flags[vf] = True
|
vol.flags[vf] = True
|
||||||
@@ -1135,6 +1136,7 @@ class AuthSrv(object):
|
|||||||
for ve, vd in (
|
for ve, vd in (
|
||||||
("sb_md", "no_sb_md"),
|
("sb_md", "no_sb_md"),
|
||||||
("sb_lg", "no_sb_lg"),
|
("sb_lg", "no_sb_lg"),
|
||||||
|
("nodotsrch", "dotsrch"),
|
||||||
):
|
):
|
||||||
if ve in vol.flags:
|
if ve in vol.flags:
|
||||||
vol.flags.pop(vd, None)
|
vol.flags.pop(vd, None)
|
||||||
|
|||||||
@@ -13,9 +13,18 @@ from pyftpdlib.filesystems import AbstractedFS, FilesystemError
|
|||||||
from pyftpdlib.handlers import FTPHandler
|
from pyftpdlib.handlers import FTPHandler
|
||||||
from pyftpdlib.servers import FTPServer
|
from pyftpdlib.servers import FTPServer
|
||||||
|
|
||||||
from .__init__ import PY2, TYPE_CHECKING, E
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, E
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import Daemon, Pebkac, exclude_dotfiles, fsenc, ipnorm
|
from .util import (
|
||||||
|
Daemon,
|
||||||
|
Pebkac,
|
||||||
|
exclude_dotfiles,
|
||||||
|
fsenc,
|
||||||
|
ipnorm,
|
||||||
|
relchk,
|
||||||
|
sanitize_fn,
|
||||||
|
vjoin,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyftpdlib.ioloop import IOLoop
|
from pyftpdlib.ioloop import IOLoop
|
||||||
@@ -125,6 +134,12 @@ class FtpFs(AbstractedFS):
|
|||||||
) -> str:
|
) -> str:
|
||||||
try:
|
try:
|
||||||
vpath = vpath.replace("\\", "/").lstrip("/")
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
rd, fn = os.path.split(vpath)
|
||||||
|
if ANYWIN and not relchk(rd):
|
||||||
|
raise FilesystemError("unsupported characters in filepath")
|
||||||
|
|
||||||
|
fn = sanitize_fn(fn or "", "", [".prologue.html", ".epilogue.html"])
|
||||||
|
vpath = vjoin(rd, fn)
|
||||||
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
|
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
|
||||||
if not vfs.realpath:
|
if not vfs.realpath:
|
||||||
raise FilesystemError("no filesystem mounted at this path")
|
raise FilesystemError("no filesystem mounted at this path")
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from .util import (
|
|||||||
guess_mime,
|
guess_mime,
|
||||||
gzip_orig_sz,
|
gzip_orig_sz,
|
||||||
hashcopy,
|
hashcopy,
|
||||||
|
hidedir,
|
||||||
html_bescape,
|
html_bescape,
|
||||||
html_escape,
|
html_escape,
|
||||||
humansize,
|
humansize,
|
||||||
@@ -64,7 +65,6 @@ from .util import (
|
|||||||
relchk,
|
relchk,
|
||||||
ren_open,
|
ren_open,
|
||||||
runhook,
|
runhook,
|
||||||
hidedir,
|
|
||||||
s3enc,
|
s3enc,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
sendfile_kern,
|
sendfile_kern,
|
||||||
@@ -605,12 +605,12 @@ class HttpCli(object):
|
|||||||
if self.is_rclone:
|
if self.is_rclone:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
cmap = {"pw": "cppwd"}
|
kv = {k: zs for k, zs in self.uparam.items() if k not in rm}
|
||||||
kv = {
|
if "pw" in kv:
|
||||||
k: zs
|
pw = self.cookies.get("cppws") or self.cookies.get("cppwd")
|
||||||
for k, zs in self.uparam.items()
|
if kv["pw"] == pw:
|
||||||
if k not in rm and self.cookies.get(cmap.get(k, k)) != zs
|
del kv["pw"]
|
||||||
}
|
|
||||||
kv.update(add)
|
kv.update(add)
|
||||||
if not kv:
|
if not kv:
|
||||||
return ""
|
return ""
|
||||||
@@ -1057,11 +1057,14 @@ class HttpCli(object):
|
|||||||
lk = parse_xml(txt)
|
lk = parse_xml(txt)
|
||||||
assert lk.tag == "{DAV:}lockinfo"
|
assert lk.tag == "{DAV:}lockinfo"
|
||||||
|
|
||||||
if not lk.find(r"./{DAV:}depth"):
|
token = str(uuid.uuid4())
|
||||||
lk.append(mktnod("D:depth", "infinity"))
|
|
||||||
|
|
||||||
lk.append(mkenod("D:timeout", mktnod("D:href", "Second-3310")))
|
if not lk.find(r"./{DAV:}depth"):
|
||||||
lk.append(mkenod("D:locktoken", mktnod("D:href", uuid.uuid4().urn)))
|
depth = self.headers.get("depth", "infinity")
|
||||||
|
lk.append(mktnod("D:depth", depth))
|
||||||
|
|
||||||
|
lk.append(mktnod("D:timeout", "Second-3310"))
|
||||||
|
lk.append(mkenod("D:locktoken", mktnod("D:href", token)))
|
||||||
lk.append(
|
lk.append(
|
||||||
mkenod("D:lockroot", mktnod("D:href", quotep(self.args.SRS + self.vpath)))
|
mkenod("D:lockroot", mktnod("D:href", quotep(self.args.SRS + self.vpath)))
|
||||||
)
|
)
|
||||||
@@ -1074,11 +1077,13 @@ class HttpCli(object):
|
|||||||
ret = '<?xml version="1.0" encoding="{}"?>\n'.format(uenc)
|
ret = '<?xml version="1.0" encoding="{}"?>\n'.format(uenc)
|
||||||
ret += ET.tostring(xroot).decode("utf-8")
|
ret += ET.tostring(xroot).decode("utf-8")
|
||||||
|
|
||||||
|
rc = 200
|
||||||
if self.can_write and not bos.path.isfile(abspath):
|
if self.can_write and not bos.path.isfile(abspath):
|
||||||
with open(fsenc(abspath), "wb") as _:
|
with open(fsenc(abspath), "wb") as _:
|
||||||
pass
|
rc = 201
|
||||||
|
|
||||||
self.reply(ret.encode(enc, "replace"), 200, "text/xml; charset=" + enc)
|
self.out_headers["Lock-Token"] = "<{}>".format(token)
|
||||||
|
self.reply(ret.encode(enc, "replace"), rc, "text/xml; charset=" + enc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handle_unlock(self) -> bool:
|
def handle_unlock(self) -> bool:
|
||||||
@@ -1388,30 +1393,14 @@ class HttpCli(object):
|
|||||||
params.update(open_ka)
|
params.update(open_ka)
|
||||||
assert fn
|
assert fn
|
||||||
|
|
||||||
if rnd and not self.args.nw:
|
if not self.args.nw:
|
||||||
fn = self.rand_name(fdir, fn, rnd)
|
if rnd:
|
||||||
|
fn = self.rand_name(fdir, fn, rnd)
|
||||||
|
|
||||||
|
fn = sanitize_fn(fn or "", "", [".prologue.html", ".epilogue.html"])
|
||||||
|
|
||||||
path = os.path.join(fdir, fn)
|
path = os.path.join(fdir, fn)
|
||||||
|
|
||||||
if is_put and not self.args.no_dav:
|
|
||||||
# allow overwrite if...
|
|
||||||
# * volflag 'daw' is set
|
|
||||||
# * and account has delete-access
|
|
||||||
# or...
|
|
||||||
# * file exists and is empty
|
|
||||||
# * and there is no .PARTIAL
|
|
||||||
|
|
||||||
tnam = fn + ".PARTIAL"
|
|
||||||
if self.args.dotpart:
|
|
||||||
tnam = "." + tnam
|
|
||||||
|
|
||||||
if (vfs.flags.get("daw") and self.can_delete) or (
|
|
||||||
not bos.path.exists(os.path.join(fdir, tnam))
|
|
||||||
and bos.path.exists(path)
|
|
||||||
and not bos.path.getsize(path)
|
|
||||||
):
|
|
||||||
params["overwrite"] = "a"
|
|
||||||
|
|
||||||
if xbu:
|
if xbu:
|
||||||
at = time.time() - lifetime
|
at = time.time() - lifetime
|
||||||
if not runhook(
|
if not runhook(
|
||||||
@@ -1430,6 +1419,26 @@ class HttpCli(object):
|
|||||||
self.log(t, 1)
|
self.log(t, 1)
|
||||||
raise Pebkac(403, t)
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
|
if is_put and not self.args.no_dav:
|
||||||
|
# allow overwrite if...
|
||||||
|
# * volflag 'daw' is set
|
||||||
|
# * and account has delete-access
|
||||||
|
# or...
|
||||||
|
# * file exists and is empty
|
||||||
|
# * and there is no .PARTIAL
|
||||||
|
|
||||||
|
tnam = fn + ".PARTIAL"
|
||||||
|
if self.args.dotpart:
|
||||||
|
tnam = "." + tnam
|
||||||
|
|
||||||
|
if (vfs.flags.get("daw") and self.can_delete) or (
|
||||||
|
not bos.path.exists(os.path.join(fdir, tnam))
|
||||||
|
and bos.path.exists(path)
|
||||||
|
and not bos.path.getsize(path)
|
||||||
|
):
|
||||||
|
# small toctou, but better than clobbering a hardlink
|
||||||
|
bos.unlink(path)
|
||||||
|
|
||||||
with ren_open(fn, *open_a, **params) as zfw:
|
with ren_open(fn, *open_a, **params) as zfw:
|
||||||
f, fn = zfw["orz"]
|
f, fn = zfw["orz"]
|
||||||
path = os.path.join(fdir, fn)
|
path = os.path.join(fdir, fn)
|
||||||
@@ -1909,9 +1918,7 @@ class HttpCli(object):
|
|||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
self.out_headerlist = [
|
self.out_headerlist = [
|
||||||
x
|
x for x in self.out_headerlist if x[0] != "Set-Cookie" or "cppw" != x[1][:4]
|
||||||
for x in self.out_headerlist
|
|
||||||
if x[0] != "Set-Cookie" or "cppwd" != x[1][:5]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
dst = self.args.SRS
|
dst = self.args.SRS
|
||||||
@@ -1943,12 +1950,12 @@ class HttpCli(object):
|
|||||||
if pwd == "x":
|
if pwd == "x":
|
||||||
# reset both plaintext and tls
|
# reset both plaintext and tls
|
||||||
# (only affects active tls cookies when tls)
|
# (only affects active tls cookies when tls)
|
||||||
for k in ("cppwd", "cppws") if self.tls else ("cppwd",):
|
for k in ("cppwd", "cppws") if self.is_https else ("cppwd",):
|
||||||
ck = gencookie(k, pwd, self.args.R, False, dur)
|
ck = gencookie(k, pwd, self.args.R, False, dur)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
else:
|
else:
|
||||||
k = "cppws" if self.tls else "cppwd"
|
k = "cppws" if self.is_https else "cppwd"
|
||||||
ck = gencookie(k, pwd, self.args.R, self.tls, dur)
|
ck = gencookie(k, pwd, self.args.R, self.is_https, dur)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
@@ -2302,7 +2309,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(400, "could not read lastmod from request")
|
raise Pebkac(400, "could not read lastmod from request")
|
||||||
|
|
||||||
nullwrite = self.args.nw
|
nullwrite = self.args.nw
|
||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, True, True)
|
||||||
self._assert_safe_rem(rem)
|
self._assert_safe_rem(rem)
|
||||||
|
|
||||||
clen = int(self.headers.get("content-length", -1))
|
clen = int(self.headers.get("content-length", -1))
|
||||||
@@ -2384,6 +2391,9 @@ class HttpCli(object):
|
|||||||
if p_field != "body":
|
if p_field != "body":
|
||||||
raise Pebkac(400, "expected body, got {}".format(p_field))
|
raise Pebkac(400, "expected body, got {}".format(p_field))
|
||||||
|
|
||||||
|
if bos.path.exists(fp):
|
||||||
|
bos.unlink(fp)
|
||||||
|
|
||||||
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
||||||
sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
|
sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
|
||||||
|
|
||||||
@@ -3353,7 +3363,7 @@ class HttpCli(object):
|
|||||||
if not self.args.no_readme and not logues[1]:
|
if not self.args.no_readme and not logues[1]:
|
||||||
for fn in ["README.md", "readme.md"]:
|
for fn in ["README.md", "readme.md"]:
|
||||||
fn = os.path.join(abspath, fn)
|
fn = os.path.join(abspath, fn)
|
||||||
if bos.path.exists(fn):
|
if bos.path.isfile(fn):
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
readme = f.read().decode("utf-8")
|
readme = f.read().decode("utf-8")
|
||||||
break
|
break
|
||||||
@@ -3493,7 +3503,9 @@ class HttpCli(object):
|
|||||||
if self.args.no_zip:
|
if self.args.no_zip:
|
||||||
margin = "DIR"
|
margin = "DIR"
|
||||||
else:
|
else:
|
||||||
margin = '<a href="{}?zip">zip</a>'.format(quotep(href))
|
margin = '<a href="{}?zip" rel="nofollow">zip</a>'.format(
|
||||||
|
quotep(href)
|
||||||
|
)
|
||||||
elif fn in hist:
|
elif fn in hist:
|
||||||
margin = '<a href="{}.hist/{}">#{}</a>'.format(
|
margin = '<a href="{}.hist/{}">#{}</a>'.format(
|
||||||
base, html_escape(hist[fn][2], quot=True, crlf=True), hist[fn][0]
|
base, html_escape(hist[fn][2], quot=True, crlf=True), hist[fn][0]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from email.utils import formatdate
|
|||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .multicast import MC_Sck, MCast
|
from .multicast import MC_Sck, MCast
|
||||||
from .util import CachedSet, min_ex, html_escape
|
from .util import CachedSet, html_escape, min_ex
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
import time
|
|
||||||
import stat
|
import stat
|
||||||
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ class U2idx(object):
|
|||||||
|
|
||||||
sret = []
|
sret = []
|
||||||
fk = flags.get("fk")
|
fk = flags.get("fk")
|
||||||
|
dots = flags.get("dotsrch")
|
||||||
c = cur.execute(uq, tuple(vuv))
|
c = cur.execute(uq, tuple(vuv))
|
||||||
for hit in c:
|
for hit in c:
|
||||||
w, ts, sz, rd, fn, ip, at = hit[:7]
|
w, ts, sz, rd, fn, ip, at = hit[:7]
|
||||||
@@ -321,6 +322,10 @@ class U2idx(object):
|
|||||||
if rd.startswith("//") or fn.startswith("//"):
|
if rd.startswith("//") or fn.startswith("//"):
|
||||||
rd, fn = s3dec(rd, fn)
|
rd, fn = s3dec(rd, fn)
|
||||||
|
|
||||||
|
rp = quotep("/".join([x for x in [vtop, rd, fn] if x]))
|
||||||
|
if not dots and "/." in ("/" + rp):
|
||||||
|
continue
|
||||||
|
|
||||||
if not fk:
|
if not fk:
|
||||||
suf = ""
|
suf = ""
|
||||||
else:
|
else:
|
||||||
@@ -337,8 +342,7 @@ class U2idx(object):
|
|||||||
)[:fk]
|
)[:fk]
|
||||||
)
|
)
|
||||||
|
|
||||||
rp = quotep("/".join([x for x in [vtop, rd, fn] if x])) + suf
|
sret.append({"ts": int(ts), "sz": sz, "rp": rp + suf, "w": w[:16]})
|
||||||
sret.append({"ts": int(ts), "sz": sz, "rp": rp, "w": w[:16]})
|
|
||||||
|
|
||||||
for hit in sret:
|
for hit in sret:
|
||||||
w = hit["w"]
|
w = hit["w"]
|
||||||
|
|||||||
@@ -1153,20 +1153,12 @@ def ren_open(
|
|||||||
fun = kwargs.pop("fun", open)
|
fun = kwargs.pop("fun", open)
|
||||||
fdir = kwargs.pop("fdir", None)
|
fdir = kwargs.pop("fdir", None)
|
||||||
suffix = kwargs.pop("suffix", None)
|
suffix = kwargs.pop("suffix", None)
|
||||||
overwrite = kwargs.pop("overwrite", None)
|
|
||||||
|
|
||||||
if fname == os.devnull:
|
if fname == os.devnull:
|
||||||
with fun(fname, *args, **kwargs) as f:
|
with fun(fname, *args, **kwargs) as f:
|
||||||
yield {"orz": (f, fname)}
|
yield {"orz": (f, fname)}
|
||||||
return
|
return
|
||||||
|
|
||||||
if overwrite:
|
|
||||||
assert fdir
|
|
||||||
fpath = os.path.join(fdir, fname)
|
|
||||||
with fun(fsenc(fpath), *args, **kwargs) as f:
|
|
||||||
yield {"orz": (f, fname)}
|
|
||||||
return
|
|
||||||
|
|
||||||
if suffix:
|
if suffix:
|
||||||
ext = fname.split(".")[-1]
|
ext = fname.split(".")[-1]
|
||||||
if len(ext) < 7:
|
if len(ext) < 7:
|
||||||
@@ -1562,7 +1554,7 @@ def gencookie(k: str, v: str, r: str, tls: bool, dur: Optional[int]) -> str:
|
|||||||
else:
|
else:
|
||||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||||
|
|
||||||
return "{}={}; Path=/{}; Expires={}; HttpOnly{}; SameSite=Lax".format(
|
return "{}={}; Path=/{}; Expires={}{}; SameSite=Lax".format(
|
||||||
k, v, r, exp, "; Secure" if tls else ""
|
k, v, r, exp, "; Secure" if tls else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -800,15 +800,20 @@ html.y #path a:hover {
|
|||||||
}
|
}
|
||||||
.logue>iframe {
|
.logue>iframe {
|
||||||
background: var(--bgg);
|
background: var(--bgg);
|
||||||
border-radius: .3em;
|
border: 1px solid var(--bgg);
|
||||||
|
border-width: 0 .3em 0 .3em;
|
||||||
|
border-radius: .5em;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
border: none;
|
margin: 0 -.3em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
.logue>iframe.focus {
|
.logue>iframe.focus {
|
||||||
box-shadow: 0 0 .1em .1em var(--a);
|
box-shadow: 0 0 .1em .1em var(--a);
|
||||||
}
|
}
|
||||||
|
#pro.logue>iframe {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
#pro.logue {
|
#pro.logue {
|
||||||
margin-bottom: .8em;
|
margin-bottom: .8em;
|
||||||
}
|
}
|
||||||
@@ -833,8 +838,9 @@ html.y #path a:hover {
|
|||||||
.mdo {
|
.mdo {
|
||||||
max-width: 52em;
|
max-width: 52em;
|
||||||
}
|
}
|
||||||
.mdo.sb {
|
.mdo.sb,
|
||||||
max-width: unset;
|
#epi.logue.mdo>iframe {
|
||||||
|
max-width: 54em;
|
||||||
}
|
}
|
||||||
.mdo,
|
.mdo,
|
||||||
.mdo * {
|
.mdo * {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<input type="file" name="f" multiple /><br />
|
<input type="file" name="f" multiple /><br />
|
||||||
<input type="submit" value="start upload">
|
<input type="submit" value="start upload">
|
||||||
</form>
|
</form>
|
||||||
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
|
<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="op_mkdir" class="opview opbox act">
|
<div id="op_mkdir" class="opview opbox act">
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
||||||
|
|
||||||
<h2><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
||||||
|
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
@@ -135,6 +135,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
var SR = {{ r|tojson }},
|
var SR = {{ r|tojson }},
|
||||||
|
TS = "{{ ts }}",
|
||||||
acct = "{{ acct }}",
|
acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
themes = {{ themes }},
|
themes = {{ themes }},
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ var Ls = {
|
|||||||
"ot_msg": "msg: send a message to the server log",
|
"ot_msg": "msg: send a message to the server log",
|
||||||
"ot_mp": "media player options",
|
"ot_mp": "media player options",
|
||||||
"ot_cfg": "configuration options",
|
"ot_cfg": "configuration options",
|
||||||
"ot_u2i": 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader<br /><br />during uploads, this icon becomes a progress indicator!',
|
"ot_u2i": 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, multithreaded, and file timestamps are preserved, but it uses more CPU than [🎈] (the basic uploader)<br /><br />during uploads, this icon becomes a progress indicator!',
|
||||||
"ot_u2w": 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$Nmultithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader<br /><br />during uploads, this icon becomes a progress indicator!',
|
"ot_u2w": 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$Nmultithreaded, and file timestamps are preserved, but it uses more CPU than [🎈] (the basic uploader)<br /><br />during uploads, this icon becomes a progress indicator!',
|
||||||
|
|
||||||
"ab_mkdir": "make directory",
|
"ab_mkdir": "make directory",
|
||||||
"ab_mkdoc": "new markdown doc",
|
"ab_mkdoc": "new markdown doc",
|
||||||
@@ -346,6 +346,7 @@ var Ls = {
|
|||||||
"s_a1": "specific metadata properties",
|
"s_a1": "specific metadata properties",
|
||||||
|
|
||||||
"md_eshow": "cannot show ",
|
"md_eshow": "cannot show ",
|
||||||
|
"md_off": "[📜<em>readme</em>] disabled in [⚙️] -- document hidden",
|
||||||
|
|
||||||
"xhr403": "403: Access denied\n\ntry pressing F5, maybe you got logged out",
|
"xhr403": "403: Access denied\n\ntry pressing F5, maybe you got logged out",
|
||||||
"cf_ok": "sorry about that -- DD" + wah + "oS protection kicked in\n\nthings should resume in about 30 sec\n\nif nothing happens, hit F5 to reload the page",
|
"cf_ok": "sorry about that -- DD" + wah + "oS protection kicked in\n\nthings should resume in about 30 sec\n\nif nothing happens, hit F5 to reload the page",
|
||||||
@@ -553,8 +554,8 @@ var Ls = {
|
|||||||
"ot_msg": "msg: send en beskjed til serverloggen",
|
"ot_msg": "msg: send en beskjed til serverloggen",
|
||||||
"ot_mp": "musikkspiller-instillinger",
|
"ot_mp": "musikkspiller-instillinger",
|
||||||
"ot_cfg": "andre innstillinger",
|
"ot_cfg": "andre innstillinger",
|
||||||
"ot_u2i": 'up2k: last opp filer (hvis du har skrivetilgang) eller bytt til søkemodus for å sjekke om filene finnes et-eller-annet sted på serveren$N$Nopplastninger kan gjenopptas etter avbrudd, skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren bup<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
|
"ot_u2i": 'up2k: last opp filer (hvis du har skrivetilgang) eller bytt til søkemodus for å sjekke om filene finnes et-eller-annet sted på serveren$N$Nopplastninger kan gjenopptas etter avbrudd, skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn [🎈] (den primitive opplasteren "bup")<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
|
||||||
"ot_u2w": 'up2k: filopplastning med støtte for å gjenoppta avbrutte opplastninger -- steng ned nettleseren og dra de samme filene inn i nettleseren igjen for å plukke opp igjen der du slapp$N$Nopplastninger skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren "bup"<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
|
"ot_u2w": 'up2k: filopplastning med støtte for å gjenoppta avbrutte opplastninger -- steng ned nettleseren og dra de samme filene inn i nettleseren igjen for å plukke opp igjen der du slapp$N$Nopplastninger skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn [🎈] (den primitive opplasteren "bup")<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
|
||||||
|
|
||||||
"ab_mkdir": "lag mappe",
|
"ab_mkdir": "lag mappe",
|
||||||
"ab_mkdoc": "nytt dokument",
|
"ab_mkdoc": "nytt dokument",
|
||||||
@@ -791,6 +792,7 @@ var Ls = {
|
|||||||
"s_a1": "konkrete egenskaper",
|
"s_a1": "konkrete egenskaper",
|
||||||
|
|
||||||
"md_eshow": "kan ikke vise ",
|
"md_eshow": "kan ikke vise ",
|
||||||
|
"md_off": "[📜<em>readme</em>] er avskrudd i [⚙️] -- dokument skjult",
|
||||||
|
|
||||||
"xhr403": "403: Tilgang nektet\n\nkanskje du ble logget ut? prøv å trykk F5",
|
"xhr403": "403: Tilgang nektet\n\nkanskje du ble logget ut? prøv å trykk F5",
|
||||||
"cf_ok": "beklager -- liten tilfeldig kontroll, alt OK\n\nting skal fortsette om ca. 30 sekunder\n\nhvis ikkeno skjer, trykk F5 for å laste siden på nytt",
|
"cf_ok": "beklager -- liten tilfeldig kontroll, alt OK\n\nting skal fortsette om ca. 30 sekunder\n\nhvis ikkeno skjer, trykk F5 for å laste siden på nytt",
|
||||||
@@ -1200,6 +1202,17 @@ function goto(dest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var SBW, SBH; // scrollbar size
|
||||||
|
(function () {
|
||||||
|
var el = mknod('div');
|
||||||
|
el.style.cssText = 'overflow:scroll;width:100px;height:100px';
|
||||||
|
document.body.appendChild(el);
|
||||||
|
SBW = el.offsetWidth - el.clientWidth;
|
||||||
|
SBH = el.offsetHeight - el.clientHeight;
|
||||||
|
document.body.removeChild(el);
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
var have_webp = sread('have_webp');
|
var have_webp = sread('have_webp');
|
||||||
(function () {
|
(function () {
|
||||||
if (have_webp !== null)
|
if (have_webp !== null)
|
||||||
@@ -5726,6 +5739,38 @@ function despin(sel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var wfp_debounce = (function () {
|
||||||
|
var r = { 'n': 0, 't': 0 };
|
||||||
|
|
||||||
|
r.hide = function () {
|
||||||
|
if (!sb_lg && !sb_md)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (++r.n <= 1) {
|
||||||
|
r.n = 1;
|
||||||
|
clearTimeout(r.t);
|
||||||
|
r.t = setTimeout(r.reset, 300);
|
||||||
|
ebi('wfp').style.opacity = 0.1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.show = function () {
|
||||||
|
if (!sb_lg && !sb_md)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (--r.n <= 0) {
|
||||||
|
r.n = 0;
|
||||||
|
clearTimeout(r.t);
|
||||||
|
ebi('wfp').style.opacity = 'unset';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.reset = function () {
|
||||||
|
r.n = 0;
|
||||||
|
r.show();
|
||||||
|
};
|
||||||
|
return r;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
function apply_perms(newperms) {
|
function apply_perms(newperms) {
|
||||||
perms = newperms || [];
|
perms = newperms || [];
|
||||||
|
|
||||||
@@ -6585,6 +6630,28 @@ var globalcss = (function () {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
var sandboxjs = (function () {
|
||||||
|
var ret = '',
|
||||||
|
busy = false,
|
||||||
|
url = SR + '/.cpr/util.js?_=' + TS,
|
||||||
|
tag = '<script src="' + url + '"></script>';
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
if (ret || busy)
|
||||||
|
return ret || tag;
|
||||||
|
|
||||||
|
var xhr = new XHR();
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (this.status == 200)
|
||||||
|
ret = '<script>' + this.responseText + '</script>';
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
busy = true;
|
||||||
|
return tag;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
function show_md(md, name, div, url, depth) {
|
function show_md(md, name, div, url, depth) {
|
||||||
var errmsg = L.md_eshow + name + ':\n\n',
|
var errmsg = L.md_eshow + name + ':\n\n',
|
||||||
@@ -6594,10 +6661,12 @@ function show_md(md, name, div, url, depth) {
|
|||||||
if (url != now)
|
if (url != now)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
wfp_debounce.hide();
|
||||||
if (!marked) {
|
if (!marked) {
|
||||||
if (depth)
|
if (depth)
|
||||||
return toast.warn(10, errmsg + 'failed to load marked.js')
|
return toast.warn(10, errmsg + 'failed to load marked.js')
|
||||||
|
|
||||||
|
wfp_debounce.n--;
|
||||||
return import_js(SR + '/.cpr/deps/marked.js', function () {
|
return import_js(SR + '/.cpr/deps/marked.js', function () {
|
||||||
show_md(md, name, div, url, 1);
|
show_md(md, name, div, url, 1);
|
||||||
});
|
});
|
||||||
@@ -6653,6 +6722,7 @@ function show_md(md, name, div, url, depth) {
|
|||||||
catch (ex) {
|
catch (ex) {
|
||||||
toast.warn(10, errmsg + ex);
|
toast.warn(10, errmsg + ex);
|
||||||
}
|
}
|
||||||
|
wfp_debounce.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6665,7 +6735,7 @@ function set_tabindex() {
|
|||||||
|
|
||||||
function show_readme(md) {
|
function show_readme(md) {
|
||||||
if (!treectl.ireadme)
|
if (!treectl.ireadme)
|
||||||
return;
|
return sandbox(ebi('epi'), '', '', 'a');
|
||||||
|
|
||||||
show_md(md, 'README.md', ebi('epi'));
|
show_md(md, 'README.md', ebi('epi'));
|
||||||
}
|
}
|
||||||
@@ -6674,16 +6744,24 @@ if (readme)
|
|||||||
|
|
||||||
|
|
||||||
function sandbox(tgt, rules, cls, html) {
|
function sandbox(tgt, rules, cls, html) {
|
||||||
|
if (!treectl.ireadme) {
|
||||||
|
tgt.innerHTML = html ? L.md_off : '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!rules || (html || '').indexOf('<') == -1) {
|
if (!rules || (html || '').indexOf('<') == -1) {
|
||||||
tgt.innerHTML = html;
|
tgt.innerHTML = html;
|
||||||
clmod(tgt, 'sb');
|
clmod(tgt, 'sb');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
clmod(tgt, 'sb', 1);
|
clmod(tgt, 'sb', 1);
|
||||||
|
|
||||||
var tid = tgt.getAttribute('id'),
|
var tid = tgt.getAttribute('id'),
|
||||||
hash = location.hash,
|
hash = location.hash,
|
||||||
want = '';
|
want = '';
|
||||||
|
|
||||||
|
if (!cls)
|
||||||
|
wfp_debounce.hide();
|
||||||
|
|
||||||
if (hash.startsWith('#md-'))
|
if (hash.startsWith('#md-'))
|
||||||
want = hash.slice(1);
|
want = hash.slice(1);
|
||||||
|
|
||||||
@@ -6696,9 +6774,8 @@ function sandbox(tgt, rules, cls, html) {
|
|||||||
|
|
||||||
html = '<html class="iframe ' + document.documentElement.className + '"><head><style>' + globalcss() +
|
html = '<html class="iframe ' + document.documentElement.className + '"><head><style>' + globalcss() +
|
||||||
'</style><base target="_parent"></head><body id="b" class="logue ' + cls + '">' + html +
|
'</style><base target="_parent"></head><body id="b" class="logue ' + cls + '">' + html +
|
||||||
'<script>' + env + '</script>' +
|
'<script>' + env + '</script>' + sandboxjs() +
|
||||||
'<script src="' + SR + '/.cpr/util.js?_={{ ts }}"></script>' +
|
'<script>var d=document.documentElement,' +
|
||||||
'<script>var ebi=document.getElementById.bind(document),d=document.documentElement,' +
|
|
||||||
'loc=new URL("' + location.href.split('?')[0] + '");' +
|
'loc=new URL("' + location.href.split('?')[0] + '");' +
|
||||||
'function say(m){window.parent.postMessage(m,"*")};' +
|
'function say(m){window.parent.postMessage(m,"*")};' +
|
||||||
'setTimeout(function(){var its=0,pih=-1,f=function(){' +
|
'setTimeout(function(){var its=0,pih=-1,f=function(){' +
|
||||||
@@ -6729,8 +6806,9 @@ window.addEventListener("message", function (e) {
|
|||||||
var t = e.data.split(/ /g);
|
var t = e.data.split(/ /g);
|
||||||
if (t[0] == 'iheight') {
|
if (t[0] == 'iheight') {
|
||||||
var el = QS(t[1] + '>iframe');
|
var el = QS(t[1] + '>iframe');
|
||||||
el.style.height = t[2] + 'px';
|
el.style.height = (parseInt(t[2]) + SBH) + 'px';
|
||||||
el.style.visibility = 'unset';
|
el.style.visibility = 'unset';
|
||||||
|
wfp_debounce.show();
|
||||||
}
|
}
|
||||||
else if (t[0] == 'iscroll') {
|
else if (t[0] == 'iscroll') {
|
||||||
var y1 = QS(t[1]).offsetTop,
|
var y1 = QS(t[1]).offsetTop,
|
||||||
|
|||||||
@@ -692,7 +692,9 @@ function noq_href(el) {
|
|||||||
|
|
||||||
|
|
||||||
function get_pwd() {
|
function get_pwd() {
|
||||||
var pwd = ('; ' + document.cookie).split('; cppwd=');
|
var k = HTTPS ? 's=' : 'd=',
|
||||||
|
pwd = ('; ' + document.cookie).split('; cppw' + k);
|
||||||
|
|
||||||
if (pwd.length < 2)
|
if (pwd.length < 2)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,21 @@
|
|||||||
|
|
||||||
# other stuff
|
# other stuff
|
||||||
|
|
||||||
|
## [`example.conf`](example.conf)
|
||||||
|
* example config file for `-c`
|
||||||
|
|
||||||
|
## [`versus.md`](versus.md)
|
||||||
|
* similar software / alternatives (with pros/cons)
|
||||||
|
|
||||||
## [`changelog.md`](changelog.md)
|
## [`changelog.md`](changelog.md)
|
||||||
* occasionally grabbed from github release notes
|
* occasionally grabbed from github release notes
|
||||||
|
|
||||||
|
## [`devnotes.md`](devnotes.md)
|
||||||
|
* technical stuff
|
||||||
|
|
||||||
## [`rclone.md`](rclone.md)
|
## [`rclone.md`](rclone.md)
|
||||||
* notes on using rclone as a fuse client/server
|
* notes on using rclone as a fuse client/server
|
||||||
|
|
||||||
## [`example.conf`](example.conf)
|
|
||||||
* example config file for `-c`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# junk
|
# junk
|
||||||
|
|||||||
@@ -1,3 +1,62 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2023-0129-1842 `v1.6.2` cors k
|
||||||
|
|
||||||
|
[Ellie Goulding - Stay Awake (kors k Hardcore Bootleg).mp3](https://a.ocv.me/pub/demo/music/.bonus/#af-134e597c)
|
||||||
|
* 👆 the read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
|
||||||
|
## breaking changes
|
||||||
|
but nothing is affected (that i know of):
|
||||||
|
* all requests must pass [cors validation](https://github.com/9001/copyparty#cors)
|
||||||
|
* but they almost definitely did already
|
||||||
|
* sharex and others are OK since they don't supply an `Origin` header
|
||||||
|
* [API calls](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#http-api) `?delete` and `?move` are now POST instead of GET
|
||||||
|
* not aware of any clients using these
|
||||||
|
|
||||||
|
## known issues
|
||||||
|
* the document sandbox is a bit laggy and sometimes eats hotkeys
|
||||||
|
* disable it with `--no-sb-md --no-sb-lg` if you trust everyone who has write and/or move access
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) -- run programs on new [uploads](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), renames, deletes
|
||||||
|
* [configurable cors](https://github.com/9001/copyparty#cors) (cross-origin resource sharing) behavior; defaults are mostly same as before
|
||||||
|
* `--allow-csrf` disables all csrf protections and makes it intentionally trivial to send authenticated requests from other domains
|
||||||
|
* sandboxed readme.md / prologues / epilogues
|
||||||
|
* documents can still run scripts like before, but can no longer tamper with the web-ui / read the login session, so the old advice of `--no-readme` and `--no-logues` is mostly deprecated
|
||||||
|
* unfortunately disables hotkeys while the text has focus + blocks dragdropping files onto that area, oh well
|
||||||
|
* password can be provided through http header `PW:` (instead of cookie `cppwd` or or url-param `?pw`)
|
||||||
|
* detect network changes (new NICs, IPs) and reconfigure / reannoucne zeroconf
|
||||||
|
* fixes mdns when running as a systemd service and copyparty is started before networking is up
|
||||||
|
* add `--freebind` to start listening on IPs before the NIC is up yet (linux-only)
|
||||||
|
* per-volume deduplication-control with volflags `hardlink`, `neversymlink`, `copydupes`
|
||||||
|
* detect curl and return a [colorful, sortable plaintext](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) directory listing instead
|
||||||
|
* add optional [powered-by-copyparty](https://user-images.githubusercontent.com/241032/215322626-11d1f02b-25f4-45df-a3d9-f8c51354a8eb.png) footnode on the controlpanel
|
||||||
|
* can be disabled with `-nb` or redirected with `--pb-url`
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* change some API calls (`?delete`, `?move`) from `GET` to `POST`
|
||||||
|
* don't panic! this was safe against authenticated csrf thanks to [SameSite=Lax](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax)
|
||||||
|
* `--getmod` restores the GETs if you need the convenience and accept the risks
|
||||||
|
* [u2cli](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) (command-line uploader):
|
||||||
|
* recover from network hiccups
|
||||||
|
* add `-ns` for slow uefi TTYs
|
||||||
|
* separate login cookies for http / https
|
||||||
|
* avoids an https login from getting accidentally sent over plaintext
|
||||||
|
* sadly no longer possible to login with internet explorer 4.0 / windows 3.11
|
||||||
|
* tar/zip-download of hidden folders
|
||||||
|
* unpost filtering was buggy for non-ascii characters
|
||||||
|
* moving a deduplicated file on a volume where deduplication was since disabled
|
||||||
|
* improved the [linux 6.0.16](https://utcc.utoronto.ca/~cks/space/blog/linux/KernelBindBugIn6016) kernel bug [workaround](https://github.com/9001/copyparty/commit/9065226c3d634a9fc15b14a768116158bc1761ad) because there is similar funk in 5.x
|
||||||
|
* add custom text selection colors because chrome is currently broken on fedora
|
||||||
|
* blockdevs (`/dev/nvme0n1`) couldn't be downloaded as files
|
||||||
|
* misc fixes for location-based reverse-proxying
|
||||||
|
* macos dualstack thing
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
* added a collection of [cursed usecases](https://github.com/9001/copyparty/tree/hovudstraum/docs/cursed-usecases)
|
||||||
|
* and [comparisons to similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) in case you ever wanna jump ship
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2023-0112-0515 `v1.5.6` many hands
|
# 2023-0112-0515 `v1.5.6` many hands
|
||||||
|
|
||||||
|
|||||||
@@ -182,13 +182,14 @@ symbol legend,
|
|||||||
| mojibake filenames | █ | | | • | • | █ | █ | • | • | • | |
|
| mojibake filenames | █ | | | • | • | █ | █ | • | • | • | |
|
||||||
| undecodable filenames | █ | | | • | • | █ | | • | • | | |
|
| undecodable filenames | █ | | | • | • | █ | | • | • | | |
|
||||||
|
|
||||||
* `zeroconf` = the server announces itself on the LAN, automatically appearing on other zeroconf-capable devices
|
* `webdav` = protocol convenient for mounting a remote server as a local filesystem; see zeroconf:
|
||||||
|
* `zeroconf` = the server announces itself on the LAN, [automatically appearing](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png) on other zeroconf-capable devices
|
||||||
* `mojibake filenames` = filenames decoded with the wrong codec and then reencoded (usually to utf-8), so `宇多田ヒカル` might look like `ëFæ╜ôcâqâJâï`
|
* `mojibake filenames` = filenames decoded with the wrong codec and then reencoded (usually to utf-8), so `宇多田ヒカル` might look like `ëFæ╜ôcâqâJâï`
|
||||||
* `undecodable filenames` = pure binary garbage which cannot be parsed as utf-8
|
* `undecodable filenames` = pure binary garbage which cannot be parsed as utf-8
|
||||||
* you can successfully play `$'\355\221'` with mpv through mounting a remote copyparty server with rclone, pog
|
* you can successfully play `$'\355\221'` with mpv through mounting a remote copyparty server with rclone, pog
|
||||||
* `a`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* extremely minimal samba/cifs server
|
* extremely minimal samba/cifs server
|
||||||
* netscape 4 / ie6 support is mostly listed as a joke altho some people have actually found it useful
|
* netscape 4 / ie6 support is mostly listed as a joke altho some people have actually found it useful ([ie4 tho](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png))
|
||||||
|
|
||||||
|
|
||||||
## server configuration
|
## server configuration
|
||||||
@@ -249,12 +250,13 @@ symbol legend,
|
|||||||
* `file action event hooks` = run script before/after upload, move, rename, ...
|
* `file action event hooks` = run script before/after upload, move, rename, ...
|
||||||
* `one-way folder sync` = like rsync, optionally deleting unexpected files at target
|
* `one-way folder sync` = like rsync, optionally deleting unexpected files at target
|
||||||
* `full sync` = stateful, dropbox-like sync
|
* `full sync` = stateful, dropbox-like sync
|
||||||
* `curl-friendly ls` = returns a plaintext folder listing when curled
|
* `curl-friendly ls` = returns a [sortable plaintext folder listing](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) when curled
|
||||||
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
||||||
* `a`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* one-way folder sync from local to server can be done efficiently with [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py), or with webdav and conventional rsync
|
* one-way folder sync from local to server can be done efficiently with [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py), or with webdav and conventional rsync
|
||||||
* can hot-reload config files (with just a few exceptions)
|
* can hot-reload config files (with just a few exceptions)
|
||||||
* can set per-folder permissions if that folder is made into a separate volume, so there is configuration overhead
|
* can set per-folder permissions if that folder is made into a separate volume, so there is configuration overhead
|
||||||
|
* [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) ([discord](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), [desktop](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png)) inspired by filebrowser, as well as the more complex [media parser](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) alternative
|
||||||
* upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py)
|
* upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py)
|
||||||
* `k`/filegator remarks:
|
* `k`/filegator remarks:
|
||||||
* `per-* permissions` -- can limit a user to one folder and its subfolders
|
* `per-* permissions` -- can limit a user to one folder and its subfolders
|
||||||
@@ -304,8 +306,10 @@ symbol legend,
|
|||||||
| copy files | | | | | █ | | | | █ | █ | █ |
|
| copy files | | | | | █ | | | | █ | █ | █ |
|
||||||
|
|
||||||
* `single-page app` = multitasking; possible to continue navigating while uploading
|
* `single-page app` = multitasking; possible to continue navigating while uploading
|
||||||
* `audio player » os-integration` = use the lockscreen to play/pause, prev/next song
|
* `audio player » os-integration` = use the [lockscreen](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) or [media hotkeys](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) to play/pause, prev/next song
|
||||||
|
* `search by custom tags` = ability to tag files through the UI and search by those
|
||||||
* `find local file` = drop a file into the browser to see if it exists on the server
|
* `find local file` = drop a file into the browser to see if it exists on the server
|
||||||
|
* `undo recent uploads` = accounts without delete permissions have a time window where they can undo their own uploads
|
||||||
* `a`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
* `a`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
||||||
* `b`/hfs2 has a very basic directory tree view, not showing sibling folders
|
* `b`/hfs2 has a very basic directory tree view, not showing sibling folders
|
||||||
* `f`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
* `f`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
||||||
@@ -324,7 +328,7 @@ symbol legend,
|
|||||||
| sharex | █ | | | █ | | █ | ╱ | █ | | | |
|
| sharex | █ | | | █ | | █ | ╱ | █ | | | |
|
||||||
| flameshot | | | | | | █ | | | | | |
|
| flameshot | | | | | | █ | | | | | |
|
||||||
|
|
||||||
* sharex ╱ = yes, but does not provide example sharex config
|
* sharex `╱` = yes, but does not provide example sharex config
|
||||||
* `a`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* `OS alert on upload` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/notify.py)
|
* `OS alert on upload` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/notify.py)
|
||||||
* `discord » announce uploads` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/discord-announce.py)
|
* `discord » announce uploads` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/discord-announce.py)
|
||||||
@@ -353,7 +357,7 @@ symbol legend,
|
|||||||
| linx | go | ░ gpl3 | 20 MB |
|
| linx | go | ░ gpl3 | 20 MB |
|
||||||
|
|
||||||
* `size` = binary (if available) or installed size of program and its dependencies
|
* `size` = binary (if available) or installed size of program and its dependencies
|
||||||
* copyparty size is for the standalone python file; the windows exe is **6 MiB**
|
* copyparty size is for the [standalone python](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) file; the [windows exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) is **6 MiB**
|
||||||
|
|
||||||
|
|
||||||
# reviews
|
# reviews
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class Cfg(Namespace):
|
|||||||
def __init__(self, a=None, v=None, c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "daw dav_inf dav_mac e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod hardlink ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nw xdev xlink xvol"
|
ex = "daw dav_inf dav_mac dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod hardlink ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nw xdev xlink xvol"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
|
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
|
||||||
|
|||||||
Reference in New Issue
Block a user