copyparty 1.19.7__py3-none-any.whl → 1.19.9__py3-none-any.whl
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/__init__.py +2 -0
- copyparty/__main__.py +60 -27
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +195 -14
- copyparty/bos/bos.py +38 -2
- copyparty/cfg.py +6 -0
- copyparty/ftpd.py +7 -25
- copyparty/httpcli.py +84 -34
- copyparty/httpsrv.py +1 -1
- copyparty/mdns.py +3 -1
- copyparty/mtag.py +0 -1
- copyparty/smbd.py +1 -1
- copyparty/svchub.py +38 -3
- copyparty/tcpsrv.py +4 -0
- copyparty/u2idx.py +29 -4
- copyparty/up2k.py +63 -29
- copyparty/util.py +49 -24
- copyparty/web/a/partyfuse.py +19 -11
- copyparty/web/a/u2c.py +6 -4
- copyparty/web/baguettebox.js.gz +0 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +1 -1
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/dbg-audio.js.gz +0 -0
- copyparty/web/deps/busy.mp3.gz +0 -0
- copyparty/web/deps/easymde.css.gz +0 -0
- copyparty/web/deps/easymde.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/deps/mini-fa.css.gz +0 -0
- copyparty/web/deps/prism.css.gz +0 -0
- copyparty/web/deps/prism.js.gz +0 -0
- copyparty/web/deps/prismd.css.gz +0 -0
- copyparty/web/deps/scp.woff2 +0 -0
- copyparty/web/deps/sha512.ac.js.gz +0 -0
- copyparty/web/idp.html +5 -5
- copyparty/web/md.css.gz +0 -0
- copyparty/web/md.html +1 -1
- copyparty/web/md.js.gz +0 -0
- copyparty/web/md2.css.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/mde.css.gz +0 -0
- copyparty/web/mde.html +1 -1
- copyparty/web/mde.js.gz +0 -0
- copyparty/web/msg.css.gz +0 -0
- copyparty/web/msg.html +1 -1
- copyparty/web/rups.css.gz +0 -0
- copyparty/web/rups.html +1 -1
- copyparty/web/rups.js.gz +0 -0
- copyparty/web/shares.css.gz +0 -0
- copyparty/web/shares.html +5 -5
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.css.gz +0 -0
- copyparty/web/splash.html +54 -38
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +80 -39
- copyparty/web/svcs.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.19.7.dist-info → copyparty-1.19.9.dist-info}/METADATA +12 -1
- copyparty-1.19.9.dist-info/RECORD +113 -0
- copyparty-1.19.7.dist-info/RECORD +0 -113
- {copyparty-1.19.7.dist-info → copyparty-1.19.9.dist-info}/WHEEL +0 -0
- {copyparty-1.19.7.dist-info → copyparty-1.19.9.dist-info}/entry_points.txt +0 -0
- {copyparty-1.19.7.dist-info → copyparty-1.19.9.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.19.7.dist-info → copyparty-1.19.9.dist-info}/top_level.txt +0 -0
copyparty/__init__.py
CHANGED
copyparty/__main__.py
CHANGED
|
@@ -36,6 +36,7 @@ from .__init__ import (
|
|
|
36
36
|
)
|
|
37
37
|
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
|
|
38
38
|
from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt
|
|
39
|
+
from .bos import bos
|
|
39
40
|
from .cfg import flagcats, onedash
|
|
40
41
|
from .svchub import SvcHub
|
|
41
42
|
from .util import (
|
|
@@ -180,7 +181,7 @@ def init_E(EE ) :
|
|
|
180
181
|
|
|
181
182
|
E = EE # pylint: disable=redefined-outer-name
|
|
182
183
|
|
|
183
|
-
def get_unixdir()
|
|
184
|
+
def get_unixdir() :
|
|
184
185
|
paths = [
|
|
185
186
|
(os.environ.get, "XDG_CONFIG_HOME"),
|
|
186
187
|
(os.path.expanduser, "~/.config"),
|
|
@@ -191,6 +192,8 @@ def init_E(EE ) :
|
|
|
191
192
|
]
|
|
192
193
|
errs = []
|
|
193
194
|
for npath, (pf, pa) in enumerate(paths):
|
|
195
|
+
priv = npath < 2 # private/trusted location
|
|
196
|
+
ram = npath > 1 # "nonvolatile"; not semantically same as `not priv`
|
|
194
197
|
p = ""
|
|
195
198
|
try:
|
|
196
199
|
p = pf(pa)
|
|
@@ -200,15 +203,21 @@ def init_E(EE ) :
|
|
|
200
203
|
p = os.path.normpath(p)
|
|
201
204
|
mkdir = not os.path.isdir(p)
|
|
202
205
|
if mkdir:
|
|
203
|
-
os.mkdir(p)
|
|
206
|
+
os.mkdir(p, 0o700)
|
|
204
207
|
|
|
205
208
|
p = os.path.join(p, "copyparty")
|
|
209
|
+
if not priv and os.path.isdir(p):
|
|
210
|
+
uid = os.geteuid()
|
|
211
|
+
if os.stat(p).st_uid != uid:
|
|
212
|
+
p += ".%s" % (uid,)
|
|
213
|
+
if os.path.isdir(p) and os.stat(p).st_uid != uid:
|
|
214
|
+
raise Exception("filesystem has broken unix permissions")
|
|
206
215
|
try:
|
|
207
216
|
os.listdir(p)
|
|
208
217
|
except:
|
|
209
|
-
os.mkdir(p)
|
|
218
|
+
os.mkdir(p, 0o700)
|
|
210
219
|
|
|
211
|
-
if
|
|
220
|
+
if ram:
|
|
212
221
|
t = "Using %s/copyparty [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
|
|
213
222
|
errs.append(t % (pa, p))
|
|
214
223
|
elif mkdir:
|
|
@@ -220,17 +229,19 @@ def init_E(EE ) :
|
|
|
220
229
|
if errs:
|
|
221
230
|
warn(". ".join(errs))
|
|
222
231
|
|
|
223
|
-
return p
|
|
232
|
+
return p, priv
|
|
224
233
|
except Exception as ex:
|
|
225
|
-
if p
|
|
234
|
+
if p:
|
|
226
235
|
t = "Unable to store config in %s [%s] due to %r"
|
|
227
236
|
errs.append(t % (pa, p, ex))
|
|
228
237
|
|
|
229
|
-
|
|
238
|
+
t = "could not find a writable path for runtime state:\n> %s"
|
|
239
|
+
raise Exception(t % ("\n> ".join(errs)))
|
|
230
240
|
|
|
231
241
|
E.mod = os.path.dirname(os.path.realpath(__file__))
|
|
232
242
|
if E.mod.endswith("__init__"):
|
|
233
243
|
E.mod = os.path.dirname(E.mod)
|
|
244
|
+
E.mod_ = os.path.join(E.mod, "")
|
|
234
245
|
|
|
235
246
|
try:
|
|
236
247
|
p = os.environ.get("XDG_CONFIG_HOME")
|
|
@@ -241,7 +252,7 @@ def init_E(EE ) :
|
|
|
241
252
|
p = os.path.abspath(os.path.realpath(p))
|
|
242
253
|
p = os.path.join(p, "copyparty")
|
|
243
254
|
if not os.path.isdir(p):
|
|
244
|
-
os.mkdir(p)
|
|
255
|
+
os.mkdir(p, 0o700)
|
|
245
256
|
os.listdir(p)
|
|
246
257
|
except:
|
|
247
258
|
p = ""
|
|
@@ -254,11 +265,11 @@ def init_E(EE ) :
|
|
|
254
265
|
elif sys.platform == "darwin":
|
|
255
266
|
E.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
|
|
256
267
|
else:
|
|
257
|
-
E.cfg = get_unixdir()
|
|
268
|
+
E.cfg, E.scfg = get_unixdir()
|
|
258
269
|
|
|
259
270
|
E.cfg = E.cfg.replace("\\", "/")
|
|
260
271
|
try:
|
|
261
|
-
|
|
272
|
+
bos.makedirs(E.cfg, bos.MKD_700)
|
|
262
273
|
except:
|
|
263
274
|
if not os.path.isdir(E.cfg):
|
|
264
275
|
raise
|
|
@@ -879,6 +890,11 @@ def get_sects():
|
|
|
879
890
|
middleware and not by clients! and, as an extra precaution,
|
|
880
891
|
send a header named '\033[36mfinalmasterspark\033[0m' (a secret keyword)
|
|
881
892
|
and then \033[36m--idp-h-key finalmasterspark\033[0m to require that
|
|
893
|
+
|
|
894
|
+
the login/logout links/buttons can be replaced with links
|
|
895
|
+
going to your IdP's UI; \033[36m--idp-login /login/?redir={dst}\033[0m
|
|
896
|
+
will expand \033[36m{dst}\033[0m to the URL of the current page, so
|
|
897
|
+
the IdP can redirect the user back to where they were
|
|
882
898
|
"""
|
|
883
899
|
),
|
|
884
900
|
],
|
|
@@ -1153,11 +1169,14 @@ def add_qr(ap, tty):
|
|
|
1153
1169
|
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
|
|
1154
1170
|
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
|
|
1155
1171
|
ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
|
|
1172
|
+
ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
|
|
1173
|
+
ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
|
|
1156
1174
|
|
|
1157
1175
|
|
|
1158
1176
|
def add_fs(ap):
|
|
1159
1177
|
ap2 = ap.add_argument_group("filesystem options")
|
|
1160
1178
|
rm_re_def = "15/0.1" if ANYWIN else "0/0"
|
|
1179
|
+
ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)")
|
|
1161
1180
|
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
|
|
1162
1181
|
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
|
|
1163
1182
|
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
|
|
@@ -1169,6 +1188,7 @@ def add_share(ap):
|
|
|
1169
1188
|
ap2 = ap.add_argument_group("share-url options")
|
|
1170
1189
|
ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
|
|
1171
1190
|
ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in")
|
|
1191
|
+
ap2.add_argument("--shr-who", metavar="TXT", type=u, default="auth", help="who can create a share? [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mauth\033[0m]=authenticated (volflag=shr_who)")
|
|
1172
1192
|
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
|
|
1173
1193
|
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
|
|
1174
1194
|
ap2.add_argument("--shr-v", action="store_true", help="debug")
|
|
@@ -1285,6 +1305,9 @@ def add_auth(ap):
|
|
|
1285
1305
|
ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
|
|
1286
1306
|
ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
|
|
1287
1307
|
ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
|
|
1308
|
+
ap2.add_argument("--idp-login", metavar="L", type=u, default="", help="replace all login-buttons with a link to URL \033[33mL\033[0m (unless \033[32mpw\033[0m is in \033[33m--auth-ord\033[0m then both will be shown); [\033[32m{dst}\033[0m] expands to url of current page")
|
|
1309
|
+
ap2.add_argument("--idp-login-t", metavar="T", type=u, default="Login with SSO", help="the label/text for the idp-login button")
|
|
1310
|
+
ap2.add_argument("--idp-logout", metavar="L", type=u, default="", help="replace all logout-buttons with a link to URL \033[33mL\033[0m")
|
|
1288
1311
|
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord")
|
|
1289
1312
|
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
|
|
1290
1313
|
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
|
@@ -1299,7 +1322,7 @@ def add_auth(ap):
|
|
|
1299
1322
|
ap2.add_argument("--ao-idp-before-pw", type=u, default="", help=argparse.SUPPRESS)
|
|
1300
1323
|
ap2.add_argument("--ao-h-before-hm", type=u, default="", help=argparse.SUPPRESS)
|
|
1301
1324
|
ap2.add_argument("--ao-ipu-wins", type=u, default="", help=argparse.SUPPRESS)
|
|
1302
|
-
ap2.add_argument("--ao-
|
|
1325
|
+
ap2.add_argument("--ao-have-pw", type=u, default="", help=argparse.SUPPRESS)
|
|
1303
1326
|
|
|
1304
1327
|
|
|
1305
1328
|
def add_chpw(ap):
|
|
@@ -1445,6 +1468,7 @@ def add_yolo(ap):
|
|
|
1445
1468
|
ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
|
|
1446
1469
|
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
|
|
1447
1470
|
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
|
|
1471
|
+
ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets")
|
|
1448
1472
|
|
|
1449
1473
|
|
|
1450
1474
|
def add_optouts(ap):
|
|
@@ -1458,7 +1482,7 @@ def add_optouts(ap):
|
|
|
1458
1482
|
ap2.add_argument("--no-fs-abrt", action="store_true", help="disable ability to abort ongoing copy/move")
|
|
1459
1483
|
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
|
1460
1484
|
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
|
1461
|
-
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
|
1485
|
+
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI. This is the same as --du-who no")
|
|
1462
1486
|
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
|
|
1463
1487
|
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)")
|
|
1464
1488
|
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)")
|
|
@@ -1652,6 +1676,7 @@ def add_db_general(ap, hcores):
|
|
|
1652
1676
|
ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
|
|
1653
1677
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
|
|
1654
1678
|
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
|
|
1679
|
+
ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags")
|
|
1655
1680
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
|
|
1656
1681
|
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")
|
|
1657
1682
|
ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
|
|
@@ -1706,7 +1731,7 @@ def add_og(ap):
|
|
|
1706
1731
|
ap2.add_argument("--uqe", action="store_true", help="query-string parceling; translate a request for \033[33m/foo/.uqe/BASE64\033[0m into \033[33m/foo?TEXT\033[0m, or \033[33m/foo/?TEXT\033[0m if the first character in \033[33mTEXT\033[0m is a slash. Automatically enabled for \033[33m--og\033[0m")
|
|
1707
1732
|
|
|
1708
1733
|
|
|
1709
|
-
def add_ui(ap, retry):
|
|
1734
|
+
def add_ui(ap, retry ):
|
|
1710
1735
|
THEMES = 10
|
|
1711
1736
|
ap2 = ap.add_argument_group("ui options")
|
|
1712
1737
|
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
|
@@ -1736,7 +1761,11 @@ def add_ui(ap, retry):
|
|
|
1736
1761
|
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
|
|
1737
1762
|
ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
|
|
1738
1763
|
ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m")
|
|
1739
|
-
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)")
|
|
1764
|
+
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as --ver-who all")
|
|
1765
|
+
ap2.add_argument("--ver-who", metavar="TXT", type=u, default="no", help="only show version for: [\033[32ma\033[0m]=admin-permission-anywhere, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone")
|
|
1766
|
+
ap2.add_argument("--du-who", metavar="TXT", type=u, default="all", help="only show disk usage for: [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mrw\033[0m]=read-write, [\033[32mw\033[0m]=write, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone (volflag=du_who)")
|
|
1767
|
+
ap2.add_argument("--ver-iwho", type=int, default=0, help=argparse.SUPPRESS)
|
|
1768
|
+
ap2.add_argument("--du-iwho", type=int, default=0, help=argparse.SUPPRESS)
|
|
1740
1769
|
ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
|
1741
1770
|
ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
|
1742
1771
|
ap2.add_argument("--ctl-re", metavar="SEC", type=int, default=1, help="the controlpanel Refresh-button will autorefresh every SEC; [\033[32m0\033[0m] = just once")
|
|
@@ -1844,18 +1873,21 @@ def run_argparse(
|
|
|
1844
1873
|
for k, h, _ in sects:
|
|
1845
1874
|
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
|
1846
1875
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
raise Exception()
|
|
1850
|
-
|
|
1876
|
+
if retry:
|
|
1877
|
+
a = ["ascii", "replace"]
|
|
1851
1878
|
for x in ap._actions:
|
|
1852
|
-
|
|
1853
|
-
|
|
1879
|
+
try:
|
|
1880
|
+
x.default = x.default.encode(*a).decode(*a)
|
|
1881
|
+
except:
|
|
1882
|
+
pass
|
|
1854
1883
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1884
|
+
try:
|
|
1885
|
+
if x.help and x.help is not argparse.SUPPRESS:
|
|
1886
|
+
x.help = x.help.replace("└─", "`-").encode(*a).decode(*a)
|
|
1887
|
+
if retry > 2:
|
|
1888
|
+
x.help = RE_ANSI.sub("", x.help)
|
|
1889
|
+
except:
|
|
1890
|
+
pass
|
|
1859
1891
|
|
|
1860
1892
|
ret = ap.parse_args(args=argv[1:])
|
|
1861
1893
|
for k, h, t in sects:
|
|
@@ -1965,7 +1997,7 @@ def main(argv = None) :
|
|
|
1965
1997
|
except:
|
|
1966
1998
|
nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
|
|
1967
1999
|
|
|
1968
|
-
retry =
|
|
2000
|
+
retry = 0
|
|
1969
2001
|
for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
|
|
1970
2002
|
try:
|
|
1971
2003
|
al = run_argparse(argv, fmtr, retry, nc)
|
|
@@ -1974,8 +2006,9 @@ def main(argv = None) :
|
|
|
1974
2006
|
except SystemExit:
|
|
1975
2007
|
raise
|
|
1976
2008
|
except:
|
|
1977
|
-
retry
|
|
1978
|
-
|
|
2009
|
+
retry += 1
|
|
2010
|
+
t = "WARNING: due to limitations in your terminal and/or OS, the helptext cannot be displayed correctly. Will show a simplified version due to the following error:\n[ %s ]:\n%s\n"
|
|
2011
|
+
lprint(t % (fmtr, min_ex()))
|
|
1979
2012
|
|
|
1980
2013
|
try:
|
|
1981
2014
|
assert al # type: ignore
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
|
@@ -13,7 +13,7 @@ import threading
|
|
|
13
13
|
import time
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
|
|
16
|
-
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
|
|
16
|
+
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
|
|
17
17
|
from .bos import bos
|
|
18
18
|
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
|
19
19
|
from .pwhash import PWHash
|
|
@@ -92,6 +92,8 @@ SBADCFG = " ({})".format(BAD_CFG)
|
|
|
92
92
|
|
|
93
93
|
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
|
|
94
94
|
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
|
|
95
|
+
PTN_U_ANY = re.compile(r"(\${[u][}%])")
|
|
96
|
+
PTN_G_ANY = re.compile(r"(\${[g][}%])")
|
|
95
97
|
PTN_SIGIL = re.compile(r"(\${[ug][}%])")
|
|
96
98
|
|
|
97
99
|
|
|
@@ -417,10 +419,14 @@ class VFS(object):
|
|
|
417
419
|
self.all_nodes[vpath] = self
|
|
418
420
|
self.all_aps = [(rp, [self])]
|
|
419
421
|
self.all_vps = [(vp, self)]
|
|
422
|
+
self.canonical = self._canonical
|
|
423
|
+
self.dcanonical = self._dcanonical
|
|
420
424
|
else:
|
|
421
425
|
self.histpath = self.dbpath = ""
|
|
422
426
|
self.all_aps = []
|
|
423
427
|
self.all_vps = []
|
|
428
|
+
self.canonical = self._canonical_null
|
|
429
|
+
self.dcanonical = self._dcanonical_null
|
|
424
430
|
|
|
425
431
|
self.get_dbv = self._get_dbv
|
|
426
432
|
self.ls = self._ls
|
|
@@ -617,7 +623,35 @@ class VFS(object):
|
|
|
617
623
|
vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem)
|
|
618
624
|
return dbv, vrem
|
|
619
625
|
|
|
620
|
-
def
|
|
626
|
+
def casechk(self, rem , do_stat ) :
|
|
627
|
+
ap = self.canonical(rem, False)
|
|
628
|
+
if do_stat and not bos.path.exists(ap):
|
|
629
|
+
return True # doesn't exist at all; good to go
|
|
630
|
+
dp, fn = os.path.split(ap)
|
|
631
|
+
try:
|
|
632
|
+
fns = os.listdir(dp)
|
|
633
|
+
except:
|
|
634
|
+
return True # maybe chmod 111; assume ok
|
|
635
|
+
if fn in fns:
|
|
636
|
+
return True
|
|
637
|
+
hit = "<?>"
|
|
638
|
+
lfn = fn.lower()
|
|
639
|
+
for zs in fns:
|
|
640
|
+
if lfn == zs.lower():
|
|
641
|
+
hit = zs
|
|
642
|
+
break
|
|
643
|
+
if self.log:
|
|
644
|
+
t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r"
|
|
645
|
+
self.log("vfs", t % (fn, hit))
|
|
646
|
+
return False
|
|
647
|
+
|
|
648
|
+
def _canonical_null(self, rem , resolve = True) :
|
|
649
|
+
return ""
|
|
650
|
+
|
|
651
|
+
def _dcanonical_null(self, rem ) :
|
|
652
|
+
return ""
|
|
653
|
+
|
|
654
|
+
def _canonical(self, rem , resolve = True) :
|
|
621
655
|
"""returns the canonical path (fully-resolved absolute fs path)"""
|
|
622
656
|
ap = self.realpath
|
|
623
657
|
if rem:
|
|
@@ -625,7 +659,7 @@ class VFS(object):
|
|
|
625
659
|
|
|
626
660
|
return absreal(ap) if resolve else ap
|
|
627
661
|
|
|
628
|
-
def
|
|
662
|
+
def _dcanonical(self, rem ) :
|
|
629
663
|
"""resolves until the final component (filename)"""
|
|
630
664
|
ap = self.realpath
|
|
631
665
|
if rem:
|
|
@@ -634,6 +668,42 @@ class VFS(object):
|
|
|
634
668
|
ad, fn = os.path.split(ap)
|
|
635
669
|
return os.path.join(absreal(ad), fn)
|
|
636
670
|
|
|
671
|
+
def _canonical_shr(self, rem , resolve = True) :
|
|
672
|
+
"""returns the canonical path (fully-resolved absolute fs path)"""
|
|
673
|
+
ap = self.realpath
|
|
674
|
+
if rem:
|
|
675
|
+
ap += "/" + rem
|
|
676
|
+
|
|
677
|
+
rap = absreal(ap)
|
|
678
|
+
if self.shr_files:
|
|
679
|
+
vn, rem = self.shr_src
|
|
680
|
+
chk = absreal(os.path.join(vn.realpath, rem))
|
|
681
|
+
if chk != rap:
|
|
682
|
+
# not the dir itself; assert file allowed
|
|
683
|
+
ad, fn = os.path.split(rap)
|
|
684
|
+
if chk != ad or fn not in self.shr_files:
|
|
685
|
+
return "\n\n"
|
|
686
|
+
|
|
687
|
+
return rap if resolve else ap
|
|
688
|
+
|
|
689
|
+
def _dcanonical_shr(self, rem ) :
|
|
690
|
+
"""resolves until the final component (filename)"""
|
|
691
|
+
ap = self.realpath
|
|
692
|
+
if rem:
|
|
693
|
+
ap += "/" + rem
|
|
694
|
+
|
|
695
|
+
ad, fn = os.path.split(ap)
|
|
696
|
+
ad = absreal(ad)
|
|
697
|
+
if self.shr_files:
|
|
698
|
+
vn, rem = self.shr_src
|
|
699
|
+
chk = absreal(os.path.join(vn.realpath, rem))
|
|
700
|
+
if chk != absreal(ap):
|
|
701
|
+
# not the dir itself; assert file allowed
|
|
702
|
+
if ad != chk or fn not in self.shr_files:
|
|
703
|
+
return "\n\n"
|
|
704
|
+
|
|
705
|
+
return os.path.join(ad, fn)
|
|
706
|
+
|
|
637
707
|
def _ls_nope(
|
|
638
708
|
self, *a, **ka
|
|
639
709
|
) :
|
|
@@ -666,8 +736,12 @@ class VFS(object):
|
|
|
666
736
|
"""return user-readable [fsdir,real,virt] items at vpath"""
|
|
667
737
|
virt_vis = {} # nodes readable by user
|
|
668
738
|
abspath = self.canonical(rem)
|
|
669
|
-
|
|
670
|
-
|
|
739
|
+
if abspath:
|
|
740
|
+
real = list(statdir(self.log, scandir, lstat, abspath, throw))
|
|
741
|
+
real.sort()
|
|
742
|
+
else:
|
|
743
|
+
real = []
|
|
744
|
+
|
|
671
745
|
if not rem:
|
|
672
746
|
# no vfs nodes in the list of real inodes
|
|
673
747
|
real = [x for x in real if x[0] not in self.nodes]
|
|
@@ -969,6 +1043,14 @@ class AuthSrv(object):
|
|
|
969
1043
|
self.indent = ""
|
|
970
1044
|
self.is_lxc = args.c == ["/z/initcfg"]
|
|
971
1045
|
|
|
1046
|
+
self._vf0b = {
|
|
1047
|
+
"tcolor": self.args.tcolor,
|
|
1048
|
+
"du_iwho": self.args.du_iwho,
|
|
1049
|
+
"shr_who": self.args.shr_who if self.args.shr else "no",
|
|
1050
|
+
}
|
|
1051
|
+
self._vf0 = self._vf0b.copy()
|
|
1052
|
+
self._vf0["d2d"] = True
|
|
1053
|
+
|
|
972
1054
|
# fwd-decl
|
|
973
1055
|
self.vfs = VFS(log_func, "", "", "", AXS(), {})
|
|
974
1056
|
self.acct = {} # uname->pw
|
|
@@ -1007,7 +1089,10 @@ class AuthSrv(object):
|
|
|
1007
1089
|
yield prev, True
|
|
1008
1090
|
|
|
1009
1091
|
def vf0(self):
|
|
1010
|
-
return
|
|
1092
|
+
return self._vf0.copy()
|
|
1093
|
+
|
|
1094
|
+
def vf0b(self):
|
|
1095
|
+
return self._vf0b.copy()
|
|
1011
1096
|
|
|
1012
1097
|
def idp_checkin(
|
|
1013
1098
|
self, broker , uname , gname
|
|
@@ -1072,6 +1157,16 @@ class AuthSrv(object):
|
|
|
1072
1157
|
src0 = src # abspath
|
|
1073
1158
|
dst0 = dst # vpath
|
|
1074
1159
|
|
|
1160
|
+
zsl = []
|
|
1161
|
+
for ptn, sigil in ((PTN_U_ANY, "${u}"), (PTN_G_ANY, "${g}")):
|
|
1162
|
+
if bool(ptn.search(src)) != bool(ptn.search(dst)):
|
|
1163
|
+
zsl.append(sigil)
|
|
1164
|
+
if zsl:
|
|
1165
|
+
t = "ERROR: if %s is mentioned in a volume definition, it must be included in both the filesystem-path [%s] and the volume-url [/%s]"
|
|
1166
|
+
t = "\n".join([t % (x, src, dst) for x in zsl])
|
|
1167
|
+
self.log(t, 1)
|
|
1168
|
+
raise Exception(t)
|
|
1169
|
+
|
|
1075
1170
|
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
|
|
1076
1171
|
if not un_gn:
|
|
1077
1172
|
# ensure volume creation if there's no users
|
|
@@ -1164,8 +1259,8 @@ class AuthSrv(object):
|
|
|
1164
1259
|
self.log(t, c=3)
|
|
1165
1260
|
raise Exception(BAD_CFG)
|
|
1166
1261
|
|
|
1167
|
-
if not bos.path.
|
|
1168
|
-
self.log("warning: filesystem-path
|
|
1262
|
+
if not bos.path.exists(src):
|
|
1263
|
+
self.log("warning: filesystem-path did not exist: %r" % (src,), 3)
|
|
1169
1264
|
|
|
1170
1265
|
mount[dst] = (src, dst0)
|
|
1171
1266
|
daxs[dst] = AXS()
|
|
@@ -1743,12 +1838,15 @@ class AuthSrv(object):
|
|
|
1743
1838
|
files = os.listdir(E.cfg)
|
|
1744
1839
|
except:
|
|
1745
1840
|
files = []
|
|
1746
|
-
hits = [
|
|
1841
|
+
hits = [
|
|
1842
|
+
x
|
|
1843
|
+
for x in files
|
|
1844
|
+
if x.lower().endswith(".conf") and not x.startswith(".")
|
|
1845
|
+
]
|
|
1747
1846
|
if hits:
|
|
1748
1847
|
t = "Hint: Found some config files in [%s], but these were not automatically loaded because they are in the wrong place%s %s\n"
|
|
1749
1848
|
self.log(t % (E.cfg, ehint, ", ".join(hits)), 3)
|
|
1750
|
-
|
|
1751
|
-
vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
|
|
1849
|
+
vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0b())
|
|
1752
1850
|
if not axs.uread:
|
|
1753
1851
|
self.badcfg1 = True
|
|
1754
1852
|
elif "" not in mount:
|
|
@@ -1784,7 +1882,7 @@ class AuthSrv(object):
|
|
|
1784
1882
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
|
1785
1883
|
vol.root = vfs
|
|
1786
1884
|
|
|
1787
|
-
zs = "neversymlink"
|
|
1885
|
+
zs = "neversymlink du_iwho"
|
|
1788
1886
|
k_ign = set(zs.split())
|
|
1789
1887
|
for vol in vfs.all_vols.values():
|
|
1790
1888
|
unknown_flags = set()
|
|
@@ -1935,6 +2033,8 @@ class AuthSrv(object):
|
|
|
1935
2033
|
promote = []
|
|
1936
2034
|
demote = []
|
|
1937
2035
|
for vol in vfs.all_vols.values():
|
|
2036
|
+
if not vol.realpath:
|
|
2037
|
+
continue
|
|
1938
2038
|
hid = self.hid_cache.get(vol.realpath)
|
|
1939
2039
|
if not hid:
|
|
1940
2040
|
zb = hashlib.sha512(afsenc(vol.realpath)).digest()
|
|
@@ -1973,6 +2073,8 @@ class AuthSrv(object):
|
|
|
1973
2073
|
vol.histpath = absreal(vol.histpath)
|
|
1974
2074
|
|
|
1975
2075
|
for vol in vfs.all_vols.values():
|
|
2076
|
+
if not vol.realpath:
|
|
2077
|
+
continue
|
|
1976
2078
|
hid = self.hid_cache[vol.realpath]
|
|
1977
2079
|
vflag = vol.flags.get("dbpath")
|
|
1978
2080
|
if vflag == "-":
|
|
@@ -2281,6 +2383,11 @@ class AuthSrv(object):
|
|
|
2281
2383
|
vol.lim.uid = vol.flags["uid"]
|
|
2282
2384
|
vol.lim.gid = vol.flags["gid"]
|
|
2283
2385
|
|
|
2386
|
+
vol.flags["du_iwho"] = n_du_who(vol.flags["du_who"])
|
|
2387
|
+
|
|
2388
|
+
if not enshare:
|
|
2389
|
+
vol.flags["shr_who"] = self.args.shr_who = "no"
|
|
2390
|
+
|
|
2284
2391
|
if vol.flags.get("og"):
|
|
2285
2392
|
self.args.uqe = True
|
|
2286
2393
|
|
|
@@ -2456,6 +2563,47 @@ class AuthSrv(object):
|
|
|
2456
2563
|
self.log(t.format(vol.vpath, mtp), 1)
|
|
2457
2564
|
errors = True
|
|
2458
2565
|
|
|
2566
|
+
for vol in vfs.all_nodes.values():
|
|
2567
|
+
if not vol.realpath or os.path.isfile(vol.realpath):
|
|
2568
|
+
continue
|
|
2569
|
+
ccs = vol.flags["casechk"][:1].lower()
|
|
2570
|
+
if ccs in ("y", "n"):
|
|
2571
|
+
if ccs == "y":
|
|
2572
|
+
vol.flags["bcasechk"] = True
|
|
2573
|
+
continue
|
|
2574
|
+
try:
|
|
2575
|
+
bos.makedirs(vol.realpath, vf=vol.flags)
|
|
2576
|
+
files = os.listdir(vol.realpath)
|
|
2577
|
+
for fn in files:
|
|
2578
|
+
fn2 = fn.lower()
|
|
2579
|
+
if fn == fn2:
|
|
2580
|
+
fn2 = fn.upper()
|
|
2581
|
+
if fn == fn2 or fn2 in files:
|
|
2582
|
+
continue
|
|
2583
|
+
is_ci = os.path.exists(os.path.join(vol.realpath, fn2))
|
|
2584
|
+
ccs = "y" if is_ci else "n"
|
|
2585
|
+
break
|
|
2586
|
+
if ccs not in ("y", "n"):
|
|
2587
|
+
ap = os.path.join(vol.realpath, "casechk")
|
|
2588
|
+
open(ap, "wb").close()
|
|
2589
|
+
ccs = "y" if os.path.exists(ap[:-1] + "K") else "n"
|
|
2590
|
+
os.unlink(ap)
|
|
2591
|
+
except Exception as ex:
|
|
2592
|
+
if ANYWIN:
|
|
2593
|
+
zs = "Windows"
|
|
2594
|
+
ccs = "y"
|
|
2595
|
+
elif MACOS:
|
|
2596
|
+
zs = "Macos"
|
|
2597
|
+
ccs = "y"
|
|
2598
|
+
else:
|
|
2599
|
+
zs = "Linux"
|
|
2600
|
+
ccs = "n"
|
|
2601
|
+
t = "unable to determine if filesystem at %r is case-insensitive due to %r; assuming casechk=%s due to %s"
|
|
2602
|
+
self.log(t % (vol.realpath, ex, ccs, zs), 3)
|
|
2603
|
+
vol.flags["casechk"] = ccs
|
|
2604
|
+
if ccs == "y":
|
|
2605
|
+
vol.flags["bcasechk"] = True
|
|
2606
|
+
|
|
2459
2607
|
tags = self.args.mtp or []
|
|
2460
2608
|
tags = [x.split("=")[0] for x in tags]
|
|
2461
2609
|
tags = [y for x in tags for y in x.split(",")]
|
|
@@ -2723,8 +2871,12 @@ class AuthSrv(object):
|
|
|
2723
2871
|
|
|
2724
2872
|
shn.shr_files = set(fns)
|
|
2725
2873
|
shn.ls = shn._ls_shr
|
|
2874
|
+
shn.canonical = shn._canonical_shr
|
|
2875
|
+
shn.dcanonical = shn._dcanonical_shr
|
|
2726
2876
|
else:
|
|
2727
2877
|
shn.ls = shn._ls
|
|
2878
|
+
shn.canonical = shn._canonical
|
|
2879
|
+
shn.dcanonical = shn._dcanonical
|
|
2728
2880
|
|
|
2729
2881
|
shn.shr_owner = s_un
|
|
2730
2882
|
shn.shr_src = (s_vfs, s_rem)
|
|
@@ -2787,6 +2939,7 @@ class AuthSrv(object):
|
|
|
2787
2939
|
"dcrop": vf["crop"],
|
|
2788
2940
|
"dth3x": vf["th3x"],
|
|
2789
2941
|
"u2ts": vf["u2ts"],
|
|
2942
|
+
"shr_who": vf["shr_who"],
|
|
2790
2943
|
"frand": bool(vf.get("rand")),
|
|
2791
2944
|
"lifetime": vf.get("lifetime") or 0,
|
|
2792
2945
|
"unlist": vf.get("unlist") or "",
|
|
@@ -2795,11 +2948,13 @@ class AuthSrv(object):
|
|
|
2795
2948
|
js_htm = {
|
|
2796
2949
|
"SPINNER": self.args.spinner,
|
|
2797
2950
|
"s_name": self.args.bname,
|
|
2951
|
+
"idp_login": self.args.idp_login,
|
|
2798
2952
|
"have_up2k_idx": "e2d" in vf,
|
|
2799
2953
|
"have_acode": not self.args.no_acode,
|
|
2800
2954
|
"have_c2flac": self.args.allow_flac,
|
|
2801
2955
|
"have_c2wav": self.args.allow_wav,
|
|
2802
2956
|
"have_shr": self.args.shr,
|
|
2957
|
+
"shr_who": vf["shr_who"],
|
|
2803
2958
|
"have_zip": not self.args.no_zip,
|
|
2804
2959
|
"have_mv": not self.args.no_mv,
|
|
2805
2960
|
"have_del": not self.args.no_del,
|
|
@@ -2865,7 +3020,7 @@ class AuthSrv(object):
|
|
|
2865
3020
|
self.args.ao_idp_before_pw = min(h, hm) < pw
|
|
2866
3021
|
self.args.ao_h_before_hm = h < hm
|
|
2867
3022
|
self.args.ao_ipu_wins = ipu == 0
|
|
2868
|
-
self.args.ao_have_pw = pw < 99
|
|
3023
|
+
self.args.ao_have_pw = pw < 99 or not self.args.have_idp_hdrs
|
|
2869
3024
|
|
|
2870
3025
|
def load_idp_db(self, quiet=False) :
|
|
2871
3026
|
# mutex me
|
|
@@ -3456,6 +3611,30 @@ class AuthSrv(object):
|
|
|
3456
3611
|
self.log("generated config:\n\n" + "\n".join(ret))
|
|
3457
3612
|
|
|
3458
3613
|
|
|
3614
|
+
def n_du_who(s ) :
|
|
3615
|
+
if s == "all":
|
|
3616
|
+
return 9
|
|
3617
|
+
if s == "auth":
|
|
3618
|
+
return 7
|
|
3619
|
+
if s == "w":
|
|
3620
|
+
return 5
|
|
3621
|
+
if s == "rw":
|
|
3622
|
+
return 4
|
|
3623
|
+
if s == "a":
|
|
3624
|
+
return 3
|
|
3625
|
+
return 0
|
|
3626
|
+
|
|
3627
|
+
|
|
3628
|
+
def n_ver_who(s ) :
|
|
3629
|
+
if s == "all":
|
|
3630
|
+
return 9
|
|
3631
|
+
if s == "auth":
|
|
3632
|
+
return 6
|
|
3633
|
+
if s == "a":
|
|
3634
|
+
return 3
|
|
3635
|
+
return 0
|
|
3636
|
+
|
|
3637
|
+
|
|
3459
3638
|
def split_cfg_ln(ln ) :
|
|
3460
3639
|
# "a, b, c: 3" => {a:true, b:true, c:3}
|
|
3461
3640
|
ret = {}
|
|
@@ -3488,7 +3667,9 @@ def expand_config_file(
|
|
|
3488
3667
|
|
|
3489
3668
|
if os.path.isdir(fp):
|
|
3490
3669
|
names = list(sorted(os.listdir(fp)))
|
|
3491
|
-
cnames = [
|
|
3670
|
+
cnames = [
|
|
3671
|
+
x for x in names if x.lower().endswith(".conf") and not x.startswith(".")
|
|
3672
|
+
]
|
|
3492
3673
|
if not cnames:
|
|
3493
3674
|
t = "warning: tried to read config-files from folder '%s' but it does not contain any "
|
|
3494
3675
|
if names:
|