copyparty 1.16.16__tar.gz → 1.16.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {copyparty-1.16.16 → copyparty-1.16.17}/PKG-INFO +12 -2
- {copyparty-1.16.16 → copyparty-1.16.17}/README.md +11 -1
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/__main__.py +15 -40
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/__version__.py +2 -2
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/authsrv.py +137 -56
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/cfg.py +8 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/fsutil.py +7 -5
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/httpcli.py +68 -32
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/svchub.py +2 -2
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/up2k.py +8 -5
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/util.py +32 -0
- copyparty-1.16.17/copyparty/web/browser.js.gz +0 -0
- copyparty-1.16.17/copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/util.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/PKG-INFO +12 -2
- copyparty-1.16.16/copyparty/web/browser.js.gz +0 -0
- copyparty-1.16.16/copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/LICENSE +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/bos/bos.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/bos/path.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/broker_mp.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/broker_thr.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/broker_util.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/cert.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/dxml.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/ftpd.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/httpconn.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/httpsrv.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/ico.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/mdns.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/metrics.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/mtag.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/multicast.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/pwhash.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/res/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/smbd.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/ssdp.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/star.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/sutil.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/szip.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/tcpsrv.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/tftpd.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/th_cli.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/th_srv.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/u2idx.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/a/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/a/partyfuse.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/a/u2c.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/baguettebox.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/browser.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/browser.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/browser2.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/cf.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dbg-audio.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dd/2.png +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dd/3.png +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dd/4.png +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dd/5.png +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/__init__.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/busy.mp3.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/easymde.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/easymde.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/fuse.py +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/prism.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/prismd.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/md.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/md.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/md.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/md2.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/md2.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/mde.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/mde.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/msg.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/msg.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/rups.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/rups.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/rups.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/shares.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/shares.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/shares.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/splash.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/splash.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/svcs.html +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/svcs.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/SOURCES.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/entry_points.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/requires.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/copyparty.egg-info/top_level.txt +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/pyproject.toml +0 -0
- {copyparty-1.16.16 → copyparty-1.16.17}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.16.
|
3
|
+
Version: 1.16.17
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
6
6
|
License: MIT
|
@@ -157,6 +157,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
157
157
|
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
|
158
158
|
* [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
|
159
159
|
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
160
|
+
* [feature beefybits](#feature-beefybits) - force-enable incompatible features
|
160
161
|
* [packages](#packages) - the party might be closer than you think
|
161
162
|
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
162
163
|
* [fedora package](#fedora-package) - does not exist yet
|
@@ -1893,7 +1894,7 @@ tell search engines you don't wanna be indexed, either using the good old [robo
|
|
1893
1894
|
* volflag `[...]:c,norobots` does the same thing for that single volume
|
1894
1895
|
* volflag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
|
1895
1896
|
|
1896
|
-
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
|
1897
|
+
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for *some* search engines -- note that crawlers which understand javascript (such as google) will not be affected
|
1897
1898
|
|
1898
1899
|
|
1899
1900
|
## themes
|
@@ -2194,6 +2195,15 @@ buggy feature? rip it out by setting any of the following environment variables
|
|
2194
2195
|
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
|
2195
2196
|
|
2196
2197
|
|
2198
|
+
### feature beefybits
|
2199
|
+
|
2200
|
+
force-enable features with known issues on your OS/env by setting any of the following environment variables, also affectionately known as `fuckitbits` or `hail-mary-bits`
|
2201
|
+
|
2202
|
+
| env-var | what it does |
|
2203
|
+
| ------------------------ | ------------ |
|
2204
|
+
| `PRTY_FORCE_MP` | force-enable multiprocessing (real multithreading) on MacOS and other broken platforms |
|
2205
|
+
|
2206
|
+
|
2197
2207
|
# packages
|
2198
2208
|
|
2199
2209
|
the party might be closer than you think
|
@@ -100,6 +100,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
100
100
|
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
|
101
101
|
* [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
|
102
102
|
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
103
|
+
* [feature beefybits](#feature-beefybits) - force-enable incompatible features
|
103
104
|
* [packages](#packages) - the party might be closer than you think
|
104
105
|
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
105
106
|
* [fedora package](#fedora-package) - does not exist yet
|
@@ -1836,7 +1837,7 @@ tell search engines you don't wanna be indexed, either using the good old [robo
|
|
1836
1837
|
* volflag `[...]:c,norobots` does the same thing for that single volume
|
1837
1838
|
* volflag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
|
1838
1839
|
|
1839
|
-
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
|
1840
|
+
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for *some* search engines -- note that crawlers which understand javascript (such as google) will not be affected
|
1840
1841
|
|
1841
1842
|
|
1842
1843
|
## themes
|
@@ -2137,6 +2138,15 @@ buggy feature? rip it out by setting any of the following environment variables
|
|
2137
2138
|
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
|
2138
2139
|
|
2139
2140
|
|
2141
|
+
### feature beefybits
|
2142
|
+
|
2143
|
+
force-enable features with known issues on your OS/env by setting any of the following environment variables, also affectionately known as `fuckitbits` or `hail-mary-bits`
|
2144
|
+
|
2145
|
+
| env-var | what it does |
|
2146
|
+
| ------------------------ | ------------ |
|
2147
|
+
| `PRTY_FORCE_MP` | force-enable multiprocessing (real multithreading) on MacOS and other broken platforms |
|
2148
|
+
|
2149
|
+
|
2140
2150
|
# packages
|
2141
2151
|
|
2142
2152
|
the party might be closer than you think
|
@@ -65,6 +65,7 @@ from .util import (
|
|
65
65
|
load_resource,
|
66
66
|
min_ex,
|
67
67
|
pybin,
|
68
|
+
read_utf8,
|
68
69
|
termsize,
|
69
70
|
wrap,
|
70
71
|
)
|
@@ -249,8 +250,7 @@ def get_srvname(verbose) :
|
|
249
250
|
if verbose:
|
250
251
|
lprint("using hostname from {}\n".format(fp))
|
251
252
|
try:
|
252
|
-
|
253
|
-
ret = f.read().decode("utf-8", "replace").strip()
|
253
|
+
return read_utf8(None, fp, True).strip()
|
254
254
|
except:
|
255
255
|
ret = ""
|
256
256
|
namelen = 5
|
@@ -259,47 +259,18 @@ def get_srvname(verbose) :
|
|
259
259
|
ret = re.sub("[234567=]", "", ret)[:namelen]
|
260
260
|
with open(fp, "wb") as f:
|
261
261
|
f.write(ret.encode("utf-8") + b"\n")
|
262
|
-
|
263
|
-
return ret
|
264
|
-
|
265
|
-
|
266
|
-
def get_fk_salt() :
|
267
|
-
fp = os.path.join(E.cfg, "fk-salt.txt")
|
268
|
-
try:
|
269
|
-
with open(fp, "rb") as f:
|
270
|
-
ret = f.read().strip()
|
271
|
-
except:
|
272
|
-
ret = b64enc(os.urandom(18))
|
273
|
-
with open(fp, "wb") as f:
|
274
|
-
f.write(ret + b"\n")
|
275
|
-
|
276
|
-
return ret.decode("utf-8")
|
277
|
-
|
278
|
-
|
279
|
-
def get_dk_salt() :
|
280
|
-
fp = os.path.join(E.cfg, "dk-salt.txt")
|
281
|
-
try:
|
282
|
-
with open(fp, "rb") as f:
|
283
|
-
ret = f.read().strip()
|
284
|
-
except:
|
285
|
-
ret = b64enc(os.urandom(30))
|
286
|
-
with open(fp, "wb") as f:
|
287
|
-
f.write(ret + b"\n")
|
288
|
-
|
289
|
-
return ret.decode("utf-8")
|
262
|
+
return ret
|
290
263
|
|
291
264
|
|
292
|
-
def
|
293
|
-
fp = os.path.join(E.cfg, "
|
265
|
+
def get_salt(name , nbytes ) :
|
266
|
+
fp = os.path.join(E.cfg, "%s-salt.txt" % (name,))
|
294
267
|
try:
|
295
|
-
|
296
|
-
ret = f.read().strip()
|
268
|
+
return read_utf8(None, fp, True).strip()
|
297
269
|
except:
|
298
|
-
ret = b64enc(os.urandom(
|
270
|
+
ret = b64enc(os.urandom(nbytes))
|
299
271
|
with open(fp, "wb") as f:
|
300
272
|
f.write(ret + b"\n")
|
301
|
-
|
302
|
-
return ret.decode("utf-8")
|
273
|
+
return ret.decode("utf-8")
|
303
274
|
|
304
275
|
|
305
276
|
def ensure_locale() :
|
@@ -1257,6 +1228,10 @@ def add_optouts(ap):
|
|
1257
1228
|
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
1258
1229
|
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
1259
1230
|
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
|
1231
|
+
ap2.add_argument("--zipmaxn", metavar="N", type=u, default="0", help="reject download-as-zip if more than \033[33mN\033[0m files in total; optionally takes a unit suffix: [\033[32m256\033[0m], [\033[32m9K\033[0m], [\033[32m4G\033[0m] (volflag=zipmaxn)")
|
1232
|
+
ap2.add_argument("--zipmaxs", metavar="SZ", type=u, default="0", help="reject download-as-zip if total download size exceeds \033[33mSZ\033[0m bytes; optionally takes a unit suffix: [\033[32m256M\033[0m], [\033[32m4G\033[0m], [\033[32m2T\033[0m] (volflag=zipmaxs)")
|
1233
|
+
ap2.add_argument("--zipmaxt", metavar="TXT", type=u, default="", help="custom errormessage when download size exceeds max (volflag=zipmaxt)")
|
1234
|
+
ap2.add_argument("--zipmaxu", action="store_true", help="authenticated users bypass the zip size limit (volflag=zipmaxu)")
|
1260
1235
|
ap2.add_argument("--zip-who", metavar="LVL", type=int, default=3, help="who can download as zip/tar? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=zip_who)\n\033[1;31mWARNING:\033[0m if a nested volume has a more restrictive value than a parent volume, then this will be \033[33mignored\033[0m if the download is initiated from the parent, more lenient volume")
|
1261
1236
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar; same as \033[33m--zip-who=0\033[0m")
|
1262
1237
|
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
@@ -1544,9 +1519,9 @@ def run_argparse(
|
|
1544
1519
|
|
1545
1520
|
cert_path = os.path.join(E.cfg, "cert.pem")
|
1546
1521
|
|
1547
|
-
fk_salt =
|
1548
|
-
dk_salt =
|
1549
|
-
ah_salt =
|
1522
|
+
fk_salt = get_salt("fk", 18)
|
1523
|
+
dk_salt = get_salt("dk", 30)
|
1524
|
+
ah_salt = get_salt("ah", 18)
|
1550
1525
|
|
1551
1526
|
# alpine peaks at 5 threads for some reason,
|
1552
1527
|
# all others scale past that (but try to avoid SMT),
|
@@ -33,6 +33,7 @@ from .util import (
|
|
33
33
|
get_df,
|
34
34
|
humansize,
|
35
35
|
odfusion,
|
36
|
+
read_utf8,
|
36
37
|
relchk,
|
37
38
|
statdir,
|
38
39
|
ub64enc,
|
@@ -64,6 +65,8 @@ SSEELOG = " ({})".format(SEE_LOG)
|
|
64
65
|
BAD_CFG = "invalid config; {}".format(SEE_LOG)
|
65
66
|
SBADCFG = " ({})".format(BAD_CFG)
|
66
67
|
|
68
|
+
PTN_U_GRP = re.compile(r"\$\{u%([+-])([^}]+)\}")
|
69
|
+
|
67
70
|
|
68
71
|
class CfgEx(Exception):
|
69
72
|
pass
|
@@ -335,22 +338,26 @@ class VFS(object):
|
|
335
338
|
log ,
|
336
339
|
realpath ,
|
337
340
|
vpath ,
|
341
|
+
vpath0 ,
|
338
342
|
axs ,
|
339
343
|
flags ,
|
340
344
|
) :
|
341
345
|
self.log = log
|
342
346
|
self.realpath = realpath # absolute path on host filesystem
|
343
347
|
self.vpath = vpath # absolute path in the virtual filesystem
|
348
|
+
self.vpath0 = vpath0 # original vpath (before idp expansion)
|
344
349
|
self.axs = axs
|
345
350
|
self.flags = flags # config options
|
346
351
|
self.root = self
|
347
352
|
self.dev = 0 # st_dev
|
353
|
+
self.badcfg1 = False
|
348
354
|
self.nodes = {} # child nodes
|
349
355
|
self.histtab = {} # all realpath->histpath
|
350
356
|
self.dbv = None # closest full/non-jump parent
|
351
357
|
self.lim = None # upload limits; only set for dbv
|
352
358
|
self.shr_src = None # source vfs+rem of a share
|
353
359
|
self.shr_files = set() # filenames to include from shr_src
|
360
|
+
self.shr_owner = "" # uname
|
354
361
|
self.aread = {}
|
355
362
|
self.awrite = {}
|
356
363
|
self.amove = {}
|
@@ -368,7 +375,7 @@ class VFS(object):
|
|
368
375
|
vp = vpath + ("/" if vpath else "")
|
369
376
|
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
370
377
|
self.all_vols = {vpath: self} # flattened recursive
|
371
|
-
self.all_nodes = {vpath: self} # also jumpvols
|
378
|
+
self.all_nodes = {vpath: self} # also jumpvols/shares
|
372
379
|
self.all_aps = [(rp, self)]
|
373
380
|
self.all_vps = [(vp, self)]
|
374
381
|
else:
|
@@ -408,7 +415,7 @@ class VFS(object):
|
|
408
415
|
for v in self.nodes.values():
|
409
416
|
v.get_all_vols(vols, nodes, aps, vps)
|
410
417
|
|
411
|
-
def add(self, src , dst ) :
|
418
|
+
def add(self, src , dst , dst0 ) :
|
412
419
|
"""get existing, or add new path to the vfs"""
|
413
420
|
assert src == "/" or not src.endswith("/") # nosec
|
414
421
|
assert not dst.endswith("/") # nosec
|
@@ -416,20 +423,22 @@ class VFS(object):
|
|
416
423
|
if "/" in dst:
|
417
424
|
# requires breadth-first population (permissions trickle down)
|
418
425
|
name, dst = dst.split("/", 1)
|
426
|
+
name0, dst0 = dst0.split("/", 1)
|
419
427
|
if name in self.nodes:
|
420
428
|
# exists; do not manipulate permissions
|
421
|
-
return self.nodes[name].add(src, dst)
|
429
|
+
return self.nodes[name].add(src, dst, dst0)
|
422
430
|
|
423
431
|
vn = VFS(
|
424
432
|
self.log,
|
425
433
|
os.path.join(self.realpath, name) if self.realpath else "",
|
426
434
|
"{}/{}".format(self.vpath, name).lstrip("/"),
|
435
|
+
"{}/{}".format(self.vpath0, name0).lstrip("/"),
|
427
436
|
self.axs,
|
428
437
|
self._copy_flags(name),
|
429
438
|
)
|
430
439
|
vn.dbv = self.dbv or self
|
431
440
|
self.nodes[name] = vn
|
432
|
-
return vn.add(src, dst)
|
441
|
+
return vn.add(src, dst, dst0)
|
433
442
|
|
434
443
|
if dst in self.nodes:
|
435
444
|
# leaf exists; return as-is
|
@@ -437,7 +446,8 @@ class VFS(object):
|
|
437
446
|
|
438
447
|
# leaf does not exist; create and keep permissions blank
|
439
448
|
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
440
|
-
|
449
|
+
vp0 = "{}/{}".format(self.vpath0, dst0).lstrip("/")
|
450
|
+
vn = VFS(self.log, src, vp, vp0, AXS(), {})
|
441
451
|
vn.dbv = self.dbv or self
|
442
452
|
self.nodes[dst] = vn
|
443
453
|
return vn
|
@@ -854,7 +864,7 @@ class AuthSrv(object):
|
|
854
864
|
self.indent = ""
|
855
865
|
|
856
866
|
# fwd-decl
|
857
|
-
self.vfs = VFS(log_func, "", "", AXS(), {})
|
867
|
+
self.vfs = VFS(log_func, "", "", "", AXS(), {})
|
858
868
|
self.acct = {} # uname->pw
|
859
869
|
self.iacct = {} # pw->uname
|
860
870
|
self.ases = {} # uname->session
|
@@ -922,7 +932,7 @@ class AuthSrv(object):
|
|
922
932
|
self,
|
923
933
|
src ,
|
924
934
|
dst ,
|
925
|
-
mount
|
935
|
+
mount ,
|
926
936
|
daxs ,
|
927
937
|
mflags ,
|
928
938
|
un_gns ,
|
@@ -938,12 +948,24 @@ class AuthSrv(object):
|
|
938
948
|
un_gn = [("", "")]
|
939
949
|
|
940
950
|
for un, gn in un_gn:
|
951
|
+
m = PTN_U_GRP.search(dst0)
|
952
|
+
if m:
|
953
|
+
req, gnc = m.groups()
|
954
|
+
hit = gnc in (un_gns.get(un) or [])
|
955
|
+
if req == "+":
|
956
|
+
if not hit:
|
957
|
+
continue
|
958
|
+
elif hit:
|
959
|
+
continue
|
960
|
+
|
941
961
|
# if ap/vp has a user/group placeholder, make sure to keep
|
942
962
|
# track so the same user/group is mapped when setting perms;
|
943
963
|
# otherwise clear un/gn to indicate it's a regular volume
|
944
964
|
|
945
965
|
src1 = src0.replace("${u}", un or "\n")
|
946
966
|
dst1 = dst0.replace("${u}", un or "\n")
|
967
|
+
src1 = PTN_U_GRP.sub(un or "\n", src1)
|
968
|
+
dst1 = PTN_U_GRP.sub(un or "\n", dst1)
|
947
969
|
if src0 == src1 and dst0 == dst1:
|
948
970
|
un = ""
|
949
971
|
|
@@ -960,7 +982,7 @@ class AuthSrv(object):
|
|
960
982
|
continue
|
961
983
|
visited.add(label)
|
962
984
|
|
963
|
-
src, dst = self._map_volume(src, dst, mount, daxs, mflags)
|
985
|
+
src, dst = self._map_volume(src, dst, dst0, mount, daxs, mflags)
|
964
986
|
if src:
|
965
987
|
ret.append((src, dst, un, gn))
|
966
988
|
if un or gn:
|
@@ -972,7 +994,8 @@ class AuthSrv(object):
|
|
972
994
|
self,
|
973
995
|
src ,
|
974
996
|
dst ,
|
975
|
-
|
997
|
+
dst0 ,
|
998
|
+
mount ,
|
976
999
|
daxs ,
|
977
1000
|
mflags ,
|
978
1001
|
) :
|
@@ -982,13 +1005,13 @@ class AuthSrv(object):
|
|
982
1005
|
|
983
1006
|
if dst in mount:
|
984
1007
|
t = "multiple filesystem-paths mounted at [/{}]:\n [{}]\n [{}]"
|
985
|
-
self.log(t.format(dst, mount[dst], src), c=1)
|
1008
|
+
self.log(t.format(dst, mount[dst][0], src), c=1)
|
986
1009
|
raise Exception(BAD_CFG)
|
987
1010
|
|
988
1011
|
if src in mount.values():
|
989
1012
|
t = "filesystem-path [{}] mounted in multiple locations:"
|
990
1013
|
t = t.format(src)
|
991
|
-
for v in [k for k, v in mount.items() if v == src] + [dst]:
|
1014
|
+
for v in [k for k, v in mount.items() if v[0] == src] + [dst]:
|
992
1015
|
t += "\n /{}".format(v)
|
993
1016
|
|
994
1017
|
self.log(t, c=3)
|
@@ -997,7 +1020,7 @@ class AuthSrv(object):
|
|
997
1020
|
if not bos.path.isdir(src):
|
998
1021
|
self.log("warning: filesystem-path does not exist: {}".format(src), 3)
|
999
1022
|
|
1000
|
-
mount[dst] = src
|
1023
|
+
mount[dst] = (src, dst0)
|
1001
1024
|
daxs[dst] = AXS()
|
1002
1025
|
mflags[dst] = {}
|
1003
1026
|
return (src, dst)
|
@@ -1058,7 +1081,7 @@ class AuthSrv(object):
|
|
1058
1081
|
grps ,
|
1059
1082
|
daxs ,
|
1060
1083
|
mflags ,
|
1061
|
-
mount
|
1084
|
+
mount ,
|
1062
1085
|
) :
|
1063
1086
|
self.line_ctr = 0
|
1064
1087
|
|
@@ -1083,7 +1106,7 @@ class AuthSrv(object):
|
|
1083
1106
|
grps ,
|
1084
1107
|
daxs ,
|
1085
1108
|
mflags ,
|
1086
|
-
mount
|
1109
|
+
mount ,
|
1087
1110
|
npass ,
|
1088
1111
|
) :
|
1089
1112
|
self.line_ctr = 0
|
@@ -1442,8 +1465,8 @@ class AuthSrv(object):
|
|
1442
1465
|
acct = {} # username:password
|
1443
1466
|
grps = {} # groupname:usernames
|
1444
1467
|
daxs = {}
|
1445
|
-
mflags = {} #
|
1446
|
-
mount
|
1468
|
+
mflags = {} # vpath:flags
|
1469
|
+
mount = {} # dst:src (vp:(ap,vp0))
|
1447
1470
|
|
1448
1471
|
self.idp_vols = {} # yolo
|
1449
1472
|
|
@@ -1522,8 +1545,8 @@ class AuthSrv(object):
|
|
1522
1545
|
# case-insensitive; normalize
|
1523
1546
|
if WINDOWS:
|
1524
1547
|
cased = {}
|
1525
|
-
for
|
1526
|
-
cased[
|
1548
|
+
for vp, (ap, vp0) in mount.items():
|
1549
|
+
cased[vp] = (absreal(ap), vp0)
|
1527
1550
|
|
1528
1551
|
mount = cased
|
1529
1552
|
|
@@ -1538,25 +1561,28 @@ class AuthSrv(object):
|
|
1538
1561
|
t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
|
1539
1562
|
self.log(t, 1)
|
1540
1563
|
axs = AXS()
|
1541
|
-
vfs = VFS(self.log_func, absreal("."), "", axs, {})
|
1564
|
+
vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
|
1565
|
+
if not axs.uread:
|
1566
|
+
vfs.badcfg1 = True
|
1542
1567
|
elif "" not in mount:
|
1543
1568
|
# there's volumes but no root; make root inaccessible
|
1544
1569
|
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
1545
|
-
vfs = VFS(self.log_func, "", "", AXS(), zsd)
|
1570
|
+
vfs = VFS(self.log_func, "", "", "", AXS(), zsd)
|
1546
1571
|
|
1547
1572
|
maxdepth = 0
|
1548
1573
|
for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
|
1549
1574
|
depth = dst.count("/")
|
1550
1575
|
assert maxdepth <= depth # nosec
|
1551
1576
|
maxdepth = depth
|
1577
|
+
src, dst0 = mount[dst]
|
1552
1578
|
|
1553
1579
|
if dst == "":
|
1554
1580
|
# rootfs was mapped; fully replaces the default CWD vfs
|
1555
|
-
vfs = VFS(self.log_func,
|
1581
|
+
vfs = VFS(self.log_func, src, dst, dst0, daxs[dst], mflags[dst])
|
1556
1582
|
continue
|
1557
1583
|
|
1558
1584
|
assert vfs # type: ignore
|
1559
|
-
zv = vfs.add(
|
1585
|
+
zv = vfs.add(src, dst, dst0)
|
1560
1586
|
zv.axs = daxs[dst]
|
1561
1587
|
zv.flags = mflags[dst]
|
1562
1588
|
zv.dbv = None
|
@@ -1590,7 +1616,8 @@ class AuthSrv(object):
|
|
1590
1616
|
if enshare:
|
1591
1617
|
import sqlite3
|
1592
1618
|
|
1593
|
-
|
1619
|
+
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
1620
|
+
shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
|
1594
1621
|
|
1595
1622
|
db_path = self.args.shr_db
|
1596
1623
|
db = sqlite3.connect(db_path)
|
@@ -1624,9 +1651,8 @@ class AuthSrv(object):
|
|
1624
1651
|
|
1625
1652
|
# don't know the abspath yet + wanna ensure the user
|
1626
1653
|
# still has the privs they granted, so nullmap it
|
1627
|
-
|
1628
|
-
|
1629
|
-
)
|
1654
|
+
vp = "%s/%s" % (shr, s_k)
|
1655
|
+
shv.nodes[s_k] = VFS(self.log_func, "", vp, vp, s_axs, shv.flags.copy())
|
1630
1656
|
|
1631
1657
|
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
1632
1658
|
for vol in shv.nodes.values():
|
@@ -1787,6 +1813,24 @@ class AuthSrv(object):
|
|
1787
1813
|
rhisttab[histp] = zv
|
1788
1814
|
vfs.histtab[zv.realpath] = histp
|
1789
1815
|
|
1816
|
+
for vol in vfs.all_vols.values():
|
1817
|
+
use = False
|
1818
|
+
for k in ["zipmaxn", "zipmaxs"]:
|
1819
|
+
try:
|
1820
|
+
zs = vol.flags[k]
|
1821
|
+
except:
|
1822
|
+
zs = getattr(self.args, k)
|
1823
|
+
if zs in ("", "0"):
|
1824
|
+
vol.flags[k] = 0
|
1825
|
+
continue
|
1826
|
+
|
1827
|
+
zf = unhumanize(zs)
|
1828
|
+
vol.flags[k + "_v"] = zf
|
1829
|
+
if zf:
|
1830
|
+
use = True
|
1831
|
+
if use:
|
1832
|
+
vol.flags["zipmax"] = True
|
1833
|
+
|
1790
1834
|
for vol in vfs.all_vols.values():
|
1791
1835
|
lim = Lim(self.log_func)
|
1792
1836
|
use = False
|
@@ -2269,22 +2313,56 @@ class AuthSrv(object):
|
|
2269
2313
|
except Pebkac:
|
2270
2314
|
self.warn_anonwrite = True
|
2271
2315
|
|
2272
|
-
|
2316
|
+
self.idp_warn = []
|
2317
|
+
self.idp_err = []
|
2273
2318
|
for idp_vp in self.idp_vols:
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2319
|
+
idp_vn, _ = vfs.get(idp_vp, "*", False, False)
|
2320
|
+
idp_vp0 = idp_vn.vpath0
|
2321
|
+
|
2322
|
+
sigils = set(re.findall(r"(\${[ug][}%])", idp_vp0))
|
2323
|
+
if len(sigils) > 1:
|
2324
|
+
t = '\nWARNING: IdP-volume "/%s" created by "/%s" has multiple IdP placeholders: %s'
|
2325
|
+
self.idp_warn.append(t % (idp_vp, idp_vp0, list(sigils)))
|
2326
|
+
continue
|
2327
|
+
|
2328
|
+
sigil = sigils.pop()
|
2329
|
+
par_vp = idp_vp
|
2330
|
+
while par_vp:
|
2331
|
+
par_vp = vsplit(par_vp)[0]
|
2332
|
+
par_vn, _ = vfs.get(par_vp, "*", False, False)
|
2333
|
+
if sigil in par_vn.vpath0:
|
2334
|
+
continue # parent was spawned for and by same user
|
2335
|
+
|
2336
|
+
oth_read = []
|
2337
|
+
oth_write = []
|
2338
|
+
for usr in par_vn.axs.uread:
|
2339
|
+
if usr not in idp_vn.axs.uread:
|
2340
|
+
oth_read.append(usr)
|
2341
|
+
for usr in par_vn.axs.uwrite:
|
2342
|
+
if usr not in idp_vn.axs.uwrite:
|
2343
|
+
oth_write.append(usr)
|
2344
|
+
|
2345
|
+
if "*" in oth_read:
|
2346
|
+
taxs = "WORLD-READABLE"
|
2347
|
+
elif "*" in oth_write:
|
2348
|
+
taxs = "WORLD-WRITABLE"
|
2349
|
+
elif oth_read:
|
2350
|
+
taxs = "READABLE BY %r" % (oth_read,)
|
2351
|
+
elif oth_write:
|
2352
|
+
taxs = "WRITABLE BY %r" % (oth_write,)
|
2353
|
+
else:
|
2354
|
+
break # no sigil; not idp; safe to stop
|
2355
|
+
|
2356
|
+
t = '\nWARNING: IdP-volume "/%s" created by "/%s" has parent/grandparent "/%s" and would be %s'
|
2357
|
+
self.idp_err.append(t % (idp_vp, idp_vp0, par_vn.vpath, taxs))
|
2358
|
+
|
2359
|
+
if self.idp_warn:
|
2360
|
+
t = "WARNING! Some IdP volumes include multiple IdP placeholders; this is too complex to automatically determine if safe or not. To ensure that no users gain unintended access, please use only a single placeholder for each IdP volume."
|
2361
|
+
self.log(t + "".join(self.idp_warn), 1)
|
2362
|
+
|
2363
|
+
if self.idp_err:
|
2364
|
+
t = "WARNING! The following IdP volumes are mounted below another volume where other users can read and/or write files. This is a SECURITY HAZARD!! When copyparty is restarted, it will not know about these IdP volumes yet. These volumes will then be accessible by an unexpected set of permissions UNTIL one of the users associated with their volume sends a request to the server. RECOMMENDATION: You should create a restricted volume where nobody can read/write files, and make sure that all IdP volumes are configured to appear somewhere below that volume."
|
2365
|
+
self.log(t + "".join(self.idp_err), 1)
|
2288
2366
|
|
2289
2367
|
self.vfs = vfs
|
2290
2368
|
self.acct = acct
|
@@ -2319,11 +2397,6 @@ class AuthSrv(object):
|
|
2319
2397
|
for x, y in vfs.all_vols.items()
|
2320
2398
|
if x != shr and not x.startswith(shrs)
|
2321
2399
|
}
|
2322
|
-
vfs.all_nodes = {
|
2323
|
-
x: y
|
2324
|
-
for x, y in vfs.all_nodes.items()
|
2325
|
-
if x != shr and not x.startswith(shrs)
|
2326
|
-
}
|
2327
2400
|
|
2328
2401
|
assert db and cur and cur2 and shv # type: ignore
|
2329
2402
|
for row in cur.execute("select * from sh"):
|
@@ -2353,6 +2426,7 @@ class AuthSrv(object):
|
|
2353
2426
|
else:
|
2354
2427
|
shn.ls = shn._ls
|
2355
2428
|
|
2429
|
+
shn.shr_owner = s_un
|
2356
2430
|
shn.shr_src = (s_vfs, s_rem)
|
2357
2431
|
shn.realpath = s_vfs.canonical(s_rem)
|
2358
2432
|
|
@@ -2370,7 +2444,7 @@ class AuthSrv(object):
|
|
2370
2444
|
continue # also fine
|
2371
2445
|
for zs in svn.nodes.keys():
|
2372
2446
|
# hide subvolume
|
2373
|
-
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
2447
|
+
vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), {})
|
2374
2448
|
|
2375
2449
|
cur2.close()
|
2376
2450
|
cur.close()
|
@@ -2378,7 +2452,9 @@ class AuthSrv(object):
|
|
2378
2452
|
|
2379
2453
|
self.js_ls = {}
|
2380
2454
|
self.js_htm = {}
|
2381
|
-
for vn in self.vfs.all_nodes.
|
2455
|
+
for vp, vn in self.vfs.all_nodes.items():
|
2456
|
+
if enshare and vp.startswith(shrs):
|
2457
|
+
continue # propagates later in this func
|
2382
2458
|
vf = vn.flags
|
2383
2459
|
vn.js_ls = {
|
2384
2460
|
"idx": "e2d" in vf,
|
@@ -2435,8 +2511,12 @@ class AuthSrv(object):
|
|
2435
2511
|
|
2436
2512
|
vols = list(vfs.all_nodes.values())
|
2437
2513
|
if enshare:
|
2438
|
-
|
2439
|
-
|
2514
|
+
for vol in shv.nodes.values():
|
2515
|
+
if vol.vpath not in vfs.all_nodes:
|
2516
|
+
self.log("BUG: /%s not in all_nodes" % (vol.vpath,), 1)
|
2517
|
+
vols.append(vol)
|
2518
|
+
if shr in vfs.all_nodes:
|
2519
|
+
self.log("BUG: %s found in all_nodes" % (shr,), 1)
|
2440
2520
|
|
2441
2521
|
for vol in vols:
|
2442
2522
|
dbv = vol.get_dbv("")[0]
|
@@ -2539,8 +2619,8 @@ class AuthSrv(object):
|
|
2539
2619
|
if not bos.path.exists(ap):
|
2540
2620
|
pwdb = {}
|
2541
2621
|
else:
|
2542
|
-
|
2543
|
-
|
2622
|
+
jtxt = read_utf8(self.log, ap, True)
|
2623
|
+
pwdb = json.loads(jtxt)
|
2544
2624
|
|
2545
2625
|
pwdb = [x for x in pwdb if x[0] != uname]
|
2546
2626
|
pwdb.append((uname, self.defpw[uname], hpw))
|
@@ -2563,8 +2643,8 @@ class AuthSrv(object):
|
|
2563
2643
|
if not self.args.chpw or not bos.path.exists(ap):
|
2564
2644
|
return
|
2565
2645
|
|
2566
|
-
|
2567
|
-
|
2646
|
+
jtxt = read_utf8(self.log, ap, True)
|
2647
|
+
pwdb = json.loads(jtxt)
|
2568
2648
|
|
2569
2649
|
useen = set()
|
2570
2650
|
urst = set()
|
@@ -3060,8 +3140,9 @@ def expand_config_file(
|
|
3060
3140
|
ipath += " -> " + fp
|
3061
3141
|
ret.append("#\033[36m opening cfg file{}\033[0m".format(ipath))
|
3062
3142
|
|
3063
|
-
|
3064
|
-
|
3143
|
+
cfg_lines = read_utf8(log, fp, True).split("\n")
|
3144
|
+
if True: # diff-golf
|
3145
|
+
for oln in [x.rstrip() for x in cfg_lines]:
|
3065
3146
|
ln = oln.split(" #")[0].strip()
|
3066
3147
|
if ln.startswith("% "):
|
3067
3148
|
pad = " " * len(oln.split("%")[0])
|
@@ -55,6 +55,7 @@ def vf_bmap() :
|
|
55
55
|
"xdev",
|
56
56
|
"xlink",
|
57
57
|
"xvol",
|
58
|
+
"zipmaxu",
|
58
59
|
):
|
59
60
|
ret[k] = k
|
60
61
|
return ret
|
@@ -101,6 +102,9 @@ def vf_vmap() :
|
|
101
102
|
"u2ts",
|
102
103
|
"ups_who",
|
103
104
|
"zip_who",
|
105
|
+
"zipmaxn",
|
106
|
+
"zipmaxs",
|
107
|
+
"zipmaxt",
|
104
108
|
):
|
105
109
|
ret[k] = k
|
106
110
|
return ret
|
@@ -299,6 +303,10 @@ flagcats = {
|
|
299
303
|
"rss": "allow '?rss' URL suffix (experimental)",
|
300
304
|
"ups_who=2": "restrict viewing the list of recent uploads",
|
301
305
|
"zip_who=2": "restrict access to download-as-zip/tar",
|
306
|
+
"zipmaxn=9k": "reject download-as-zip if more than 9000 files",
|
307
|
+
"zipmaxs=2g": "reject download-as-zip if size over 2 GiB",
|
308
|
+
"zipmaxt=no": "reply with 'no' if download-as-zip exceeds max",
|
309
|
+
"zipmaxu": "zip-size-limit does not apply to authenticated users",
|
302
310
|
"nopipe": "disable race-the-beam (download unfinished uploads)",
|
303
311
|
"mv_retry": "ms-windows: timeout for renaming busy files",
|
304
312
|
"rm_retry": "ms-windows: timeout for deleting busy files",
|