copyparty 1.19.5__py3-none-any.whl → 1.19.6__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/__main__.py +82 -31
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +16 -1
- copyparty/broker_util.py +0 -1
- copyparty/cert.py +1 -0
- copyparty/cfg.py +2 -0
- copyparty/ftpd.py +1 -1
- copyparty/httpcli.py +43 -27
- copyparty/mdns.py +2 -2
- copyparty/multicast.py +3 -3
- copyparty/pwhash.py +1 -0
- copyparty/smbd.py +1 -1
- copyparty/svchub.py +54 -17
- copyparty/tcpsrv.py +4 -3
- copyparty/up2k.py +13 -2
- copyparty/util.py +30 -0
- copyparty/web/a/u2c.py +6 -6
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/md.html +2 -1
- copyparty/web/md.js.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/mde.html +2 -1
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/METADATA +11 -1
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/RECORD +30 -30
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/WHEEL +0 -0
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/entry_points.txt +0 -0
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.19.5.dist-info → copyparty-1.19.6.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -190,35 +190,41 @@ def init_E(EE ) :
|
|
190
190
|
(unicode, "/tmp"),
|
191
191
|
]
|
192
192
|
errs = []
|
193
|
-
for
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
193
|
+
for npath, (pf, pa) in enumerate(paths):
|
194
|
+
p = ""
|
195
|
+
try:
|
196
|
+
p = pf(pa)
|
197
|
+
if not p or p.startswith("~"):
|
198
|
+
continue
|
199
|
+
|
200
|
+
p = os.path.normpath(p)
|
201
|
+
if os.path.isdir(p) and os.listdir(p):
|
202
|
+
mkdir = False
|
203
|
+
else:
|
204
|
+
mkdir = True
|
205
|
+
os.mkdir(p)
|
206
|
+
|
207
|
+
p = os.path.join(p, "copyparty")
|
208
|
+
if not os.path.isdir(p):
|
209
|
+
os.mkdir(p)
|
210
|
+
|
211
|
+
if npath > 1:
|
212
|
+
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
|
+
errs.append(t % (pa, p))
|
214
|
+
elif mkdir:
|
215
|
+
t = "Using %s/copyparty [%s] for config%s (Warning: %s did not exist and was created just now)"
|
216
|
+
errs.append(t % (pa, p, " instead" if npath else "", pa))
|
217
|
+
elif errs:
|
218
|
+
errs.append("Using %s/copyparty [%s] instead" % (pa, p))
|
219
|
+
|
220
|
+
if errs:
|
221
|
+
warn(". ".join(errs))
|
222
|
+
|
223
|
+
return p # type: ignore
|
224
|
+
except Exception as ex:
|
225
|
+
if p and npath < 2:
|
226
|
+
t = "Unable to store config in %s [%s] due to %r"
|
227
|
+
errs.append(t % (pa, p, ex))
|
222
228
|
|
223
229
|
raise Exception("could not find a writable path for config")
|
224
230
|
|
@@ -662,6 +668,42 @@ def get_sects():
|
|
662
668
|
"""
|
663
669
|
),
|
664
670
|
],
|
671
|
+
[
|
672
|
+
"auth-ord",
|
673
|
+
"authentication precedence",
|
674
|
+
dedent(
|
675
|
+
"""
|
676
|
+
\033[33m--auth-ord\033[0m is a comma-separated list of auth options
|
677
|
+
(one or more of the [\033[35moptions\033[0m] below); first one wins
|
678
|
+
|
679
|
+
[\033[35mpw\033[0m] is conventional login, for example the "\033[36mPW\033[0m" header,
|
680
|
+
or the \033[36m?pw=\033[0m[...] URL-suffix, or a valid session cookie
|
681
|
+
(see \033[33m--help-auth\033[0m)
|
682
|
+
|
683
|
+
[\033[35midp\033[0m] is a username provided in the http-request-header
|
684
|
+
defined by \033[33m--idp-h-usr\033[0m and/or \033[33m--idp-hm-usr\033[0m, which is
|
685
|
+
provided by an authentication middleware such as
|
686
|
+
authentik, authelia, tailscale, ... (see \033[33m--help-idp\033[0m)
|
687
|
+
|
688
|
+
[\033[35midp-h\033[0m] is specifically an \033[33m--idp-h-usr\033[0m header,
|
689
|
+
[\033[35midp-hm\033[0m] is specifically an \033[33m--idp-hm-usr\033[0m header;
|
690
|
+
[\033[35midp\033[0m] is the same as [\033[35midp-hm,idp-h\033[0m]
|
691
|
+
|
692
|
+
[\033[35mipu\033[0m] is a mapping from an IP-address to a username,
|
693
|
+
auto-authing that client-IP to that account
|
694
|
+
(see the description of \033[36m--ipu\033[0m in \033[33m--help\033[0m)
|
695
|
+
|
696
|
+
NOTE: even if an option (\033[35mpw\033[0m/\033[35mipu\033[0m/...) is not in the list,
|
697
|
+
it may still be enabled and can still take effect if
|
698
|
+
none of the other alternatives identify the user
|
699
|
+
|
700
|
+
NOTE: if [\033[35mipu\033[0m] is in the list, it must be FIRST or LAST
|
701
|
+
|
702
|
+
NOTE: if [\033[35mpw\033[0m] is not in the list, the logout-button
|
703
|
+
will be hidden when any idp feature is enabled
|
704
|
+
"""
|
705
|
+
),
|
706
|
+
],
|
665
707
|
[
|
666
708
|
"flags",
|
667
709
|
"list of volflags",
|
@@ -1107,6 +1149,8 @@ def add_qr(ap, tty):
|
|
1107
1149
|
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
|
1108
1150
|
ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr")
|
1109
1151
|
ap2.add_argument("--qr-wait", metavar="SEC", type=float, default=0, help="wait \033[33mSEC\033[0m before printing the qr-code to the log")
|
1152
|
+
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)")
|
1153
|
+
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")
|
1110
1154
|
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")
|
1111
1155
|
|
1112
1156
|
|
@@ -1232,7 +1276,7 @@ def add_auth(ap):
|
|
1232
1276
|
ses_db = os.path.join(E.cfg, "sessions.db")
|
1233
1277
|
ap2 = ap.add_argument_group("IdP / identity provider / user authentication options")
|
1234
1278
|
ap2.add_argument("--idp-h-usr", metavar="HN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
|
1235
|
-
ap2.add_argument("--idp-hm-usr", metavar="
|
1279
|
+
ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see --help-idp")
|
1236
1280
|
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
|
1237
1281
|
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
|
1238
1282
|
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
|
@@ -1240,6 +1284,7 @@ def add_auth(ap):
|
|
1240
1284
|
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")
|
1241
1285
|
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)")
|
1242
1286
|
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)")
|
1287
|
+
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")
|
1243
1288
|
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")
|
1244
1289
|
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")
|
1245
1290
|
ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
|
@@ -1250,6 +1295,10 @@ def add_auth(ap):
|
|
1250
1295
|
ap2.add_argument("--ipr", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m username \033[33mUSR\033[0m can only connect from an IP matching one or more \033[33mCIDR\033[0m (comma-sep.); example: [\033[32m192.168.123.0/24,172.16.0.0/16=dave]")
|
1251
1296
|
ap2.add_argument("--have-idp-hdrs", type=u, default="", help=argparse.SUPPRESS)
|
1252
1297
|
ap2.add_argument("--have-ipu-or-ipr", type=u, default="", help=argparse.SUPPRESS)
|
1298
|
+
ap2.add_argument("--ao-idp-before-pw", type=u, default="", help=argparse.SUPPRESS)
|
1299
|
+
ap2.add_argument("--ao-h-before-hm", type=u, default="", help=argparse.SUPPRESS)
|
1300
|
+
ap2.add_argument("--ao-ipu-wins", type=u, default="", help=argparse.SUPPRESS)
|
1301
|
+
ap2.add_argument("--ao-has-pw", type=u, default="", help=argparse.SUPPRESS)
|
1253
1302
|
|
1254
1303
|
|
1255
1304
|
def add_chpw(ap):
|
@@ -1489,6 +1538,7 @@ def add_logging(ap):
|
|
1489
1538
|
ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)")
|
1490
1539
|
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
|
1491
1540
|
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
|
1541
|
+
ap2.add_argument("--log-badxml", action="store_true", help="log any invalid XML received from a client")
|
1492
1542
|
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
1493
1543
|
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
|
1494
1544
|
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
|
@@ -1626,6 +1676,7 @@ def add_db_metadata(ap):
|
|
1626
1676
|
|
1627
1677
|
def add_txt(ap):
|
1628
1678
|
ap2 = ap.add_argument_group("textfile options")
|
1679
|
+
ap2.add_argument("--md-no-br", action="store_true", help="markdown: disable newline-is-newline; will only render a newline into the html given two trailing spaces or a double-newline (volflag=md_no_br)")
|
1629
1680
|
ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
|
1630
1681
|
ap2.add_argument("--txt-eol", metavar="TYPE", type=u, default="", help="enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol)")
|
1631
1682
|
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds")
|
@@ -1959,7 +2010,7 @@ def main(argv = None) :
|
|
1959
2010
|
if not HAVE_IPV6 and al.i == "::":
|
1960
2011
|
al.i = "0.0.0.0"
|
1961
2012
|
|
1962
|
-
al.i = al.i.split(",")
|
2013
|
+
al.i = [x.strip() for x in al.i.split(",")]
|
1963
2014
|
try:
|
1964
2015
|
if "-" in al.p:
|
1965
2016
|
lo, hi = [int(x) for x in al.p.split("-")]
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -1651,6 +1651,7 @@ class AuthSrv(object):
|
|
1651
1651
|
# accept both , and : as separators between usernames
|
1652
1652
|
zs1, zs2 = x.replace("=", ":").split(":", 1)
|
1653
1653
|
grps[zs1] = zs2.replace(":", ",").split(",")
|
1654
|
+
grps[zs1] = [x.strip() for x in grps[zs1]]
|
1654
1655
|
except:
|
1655
1656
|
t = '\n invalid value "{}" for argument --grp, must be groupname:username1,username2,...'
|
1656
1657
|
raise Exception(t.format(x))
|
@@ -1704,6 +1705,7 @@ class AuthSrv(object):
|
|
1704
1705
|
|
1705
1706
|
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
|
1706
1707
|
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
|
1708
|
+
self.setup_auth_ord()
|
1707
1709
|
|
1708
1710
|
self.setup_pwhash(acct)
|
1709
1711
|
defpw = acct.copy()
|
@@ -2802,7 +2804,8 @@ class AuthSrv(object):
|
|
2802
2804
|
"have_mv": not self.args.no_mv,
|
2803
2805
|
"have_del": not self.args.no_del,
|
2804
2806
|
"have_unpost": int(self.args.unpost),
|
2805
|
-
"have_emp": self.args.emp,
|
2807
|
+
"have_emp": int(self.args.emp),
|
2808
|
+
"md_no_br": int(vf.get("md_no_br") or 0),
|
2806
2809
|
"ext_th": vf.get("ext_th_d") or {},
|
2807
2810
|
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
2808
2811
|
"sba_md": vf.get("md_sba") or "",
|
@@ -2852,6 +2855,18 @@ class AuthSrv(object):
|
|
2852
2855
|
zs = str(vol.flags.get("tcolor") or self.args.tcolor)
|
2853
2856
|
vol.flags["tcolor"] = zs.lstrip("#")
|
2854
2857
|
|
2858
|
+
def setup_auth_ord(self) :
|
2859
|
+
ao = [x.strip() for x in self.args.auth_ord.split(",")]
|
2860
|
+
if "idp" in ao:
|
2861
|
+
zi = ao.index("idp")
|
2862
|
+
ao = ao[:zi] + ["idp-hm", "idp-h"] + ao[zi:]
|
2863
|
+
zsl = "pw idp-h idp-hm ipu".split()
|
2864
|
+
pw, h, hm, ipu = [ao.index(x) if x in ao else 99 for x in zsl]
|
2865
|
+
self.args.ao_idp_before_pw = min(h, hm) < pw
|
2866
|
+
self.args.ao_h_before_hm = h < hm
|
2867
|
+
self.args.ao_ipu_wins = ipu == 0
|
2868
|
+
self.args.ao_have_pw = pw < 99
|
2869
|
+
|
2855
2870
|
def load_idp_db(self, quiet=False) :
|
2856
2871
|
# mutex me
|
2857
2872
|
level = self.args.idp_store
|
copyparty/broker_util.py
CHANGED
copyparty/cert.py
CHANGED
@@ -126,6 +126,7 @@ def _gen_srv(log , args, netdevs ):
|
|
126
126
|
nlog = lambda msg, c=0: log("cert-gen-srv", msg, c)
|
127
127
|
|
128
128
|
names = args.crt_ns.split(",") if args.crt_ns else []
|
129
|
+
names = [x.strip() for x in names]
|
129
130
|
if not args.crt_exact:
|
130
131
|
for n in names[:]:
|
131
132
|
names.append("*.{}".format(n))
|
copyparty/cfg.py
CHANGED
@@ -44,6 +44,7 @@ def vf_bmap() :
|
|
44
44
|
"gsel",
|
45
45
|
"hardlink",
|
46
46
|
"magic",
|
47
|
+
"md_no_br",
|
47
48
|
"no_db_ip",
|
48
49
|
"no_sb_md",
|
49
50
|
"no_sb_lg",
|
@@ -324,6 +325,7 @@ flagcats = {
|
|
324
325
|
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
325
326
|
},
|
326
327
|
"textfiles": {
|
328
|
+
"md_no_br": "newline only on double-newline or two tailing spaces",
|
327
329
|
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
|
328
330
|
"exp": "enable textfile expansion; see --help-exp",
|
329
331
|
"exp_md": "placeholders to expand in markdown files; see --help",
|
copyparty/ftpd.py
CHANGED
@@ -382,7 +382,7 @@ class FtpFs(AbstractedFS):
|
|
382
382
|
svp = join(self.cwd, src).lstrip("/")
|
383
383
|
dvp = join(self.cwd, dst).lstrip("/")
|
384
384
|
try:
|
385
|
-
self.hub.up2k.handle_mv(self.uname, self.h.cli_ip, svp, dvp)
|
385
|
+
self.hub.up2k.handle_mv("", self.uname, self.h.cli_ip, svp, dvp)
|
386
386
|
except Exception as ex:
|
387
387
|
raise FSE(str(ex))
|
388
388
|
|
copyparty/httpcli.py
CHANGED
@@ -12,7 +12,6 @@ import random
|
|
12
12
|
import re
|
13
13
|
import socket
|
14
14
|
import stat
|
15
|
-
import string
|
16
15
|
import sys
|
17
16
|
import threading # typechk
|
18
17
|
import time
|
@@ -31,7 +30,7 @@ try:
|
|
31
30
|
except:
|
32
31
|
pass
|
33
32
|
|
34
|
-
from .__init__ import ANYWIN,
|
33
|
+
from .__init__ import ANYWIN, RES, TYPE_CHECKING, EnvParams, unicode
|
35
34
|
from .__version__ import S_VERSION
|
36
35
|
from .authsrv import LEELOO_DALLAS, VFS # typechk
|
37
36
|
from .bos import bos
|
@@ -66,6 +65,7 @@ from .util import (
|
|
66
65
|
exclude_dotfiles,
|
67
66
|
formatdate,
|
68
67
|
fsenc,
|
68
|
+
gen_content_disposition,
|
69
69
|
gen_filekey,
|
70
70
|
gen_filekey_dbg,
|
71
71
|
gencookie,
|
@@ -619,7 +619,9 @@ class HttpCli(object):
|
|
619
619
|
or "*"
|
620
620
|
)
|
621
621
|
|
622
|
-
if self.args.have_idp_hdrs
|
622
|
+
if self.args.have_idp_hdrs and (
|
623
|
+
self.uname == "*" or self.args.ao_idp_before_pw
|
624
|
+
):
|
623
625
|
idp_usr = ""
|
624
626
|
if self.args.idp_hm_usr:
|
625
627
|
for hn, hmv in self.args.idp_hm_usr_p.items():
|
@@ -632,9 +634,9 @@ class HttpCli(object):
|
|
632
634
|
if idp_usr:
|
633
635
|
break
|
634
636
|
for hn in self.args.idp_h_usr:
|
635
|
-
if idp_usr:
|
637
|
+
if idp_usr and not self.args.ao_h_before_hm:
|
636
638
|
break
|
637
|
-
idp_usr = self.headers.get(hn)
|
639
|
+
idp_usr = self.headers.get(hn) or idp_usr
|
638
640
|
if idp_usr:
|
639
641
|
idp_grp = (
|
640
642
|
self.headers.get(self.args.idp_h_grp) or ""
|
@@ -683,7 +685,10 @@ class HttpCli(object):
|
|
683
685
|
if idp_usr in self.asrv.vfs.aread:
|
684
686
|
self.pw = ""
|
685
687
|
self.uname = idp_usr
|
686
|
-
self.
|
688
|
+
if self.args.ao_have_pw:
|
689
|
+
self.html_head += "<script>var is_idp=1</script>\n"
|
690
|
+
else:
|
691
|
+
self.html_head += "<script>var is_idp=2</script>\n"
|
687
692
|
zs = self.asrv.ases.get(idp_usr)
|
688
693
|
if zs:
|
689
694
|
self.set_idp_cookie(zs)
|
@@ -691,7 +696,7 @@ class HttpCli(object):
|
|
691
696
|
self.log("unknown username: %r" % (idp_usr,), 1)
|
692
697
|
|
693
698
|
if self.args.have_ipu_or_ipr:
|
694
|
-
if self.args.ipu and self.uname == "*":
|
699
|
+
if self.args.ipu and (self.uname == "*" or self.args.ao_ipu_wins):
|
695
700
|
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
696
701
|
ipr = self.conn.hsrv.ipr
|
697
702
|
if ipr and self.uname in ipr:
|
@@ -809,6 +814,15 @@ class HttpCli(object):
|
|
809
814
|
6 if em.startswith("client d/c ") else 3,
|
810
815
|
)
|
811
816
|
|
817
|
+
if self.hint and self.hint.startswith("<xml> "):
|
818
|
+
if self.args.log_badxml:
|
819
|
+
t = "invalid XML received from client: %r"
|
820
|
+
self.log(t % (self.hint[6:],), 6)
|
821
|
+
else:
|
822
|
+
t = "received invalid XML from client; enable --log-badxml to see the whole XML in the log"
|
823
|
+
self.log(t, 6)
|
824
|
+
self.hint = ""
|
825
|
+
|
812
826
|
msg = "%s\r\nURL: %s\r\n" % (em, self.vpath)
|
813
827
|
if self.hint:
|
814
828
|
msg += "hint: %s\r\n" % (self.hint,)
|
@@ -1525,7 +1539,9 @@ class HttpCli(object):
|
|
1525
1539
|
if not rbuf or len(buf) >= 32768:
|
1526
1540
|
break
|
1527
1541
|
|
1528
|
-
|
1542
|
+
sbuf = buf.decode(enc, "replace")
|
1543
|
+
self.hint = "<xml> " + sbuf
|
1544
|
+
xroot = parse_xml(sbuf)
|
1529
1545
|
xtag = next((x for x in xroot if x.tag.split("}")[-1] == "prop"), None)
|
1530
1546
|
if xtag is not None:
|
1531
1547
|
props = set([y.tag.split("}")[-1] for y in xtag])
|
@@ -1731,6 +1747,7 @@ class HttpCli(object):
|
|
1731
1747
|
uenc = enc.upper()
|
1732
1748
|
|
1733
1749
|
txt = buf.decode(enc, "replace")
|
1750
|
+
self.hint = "<xml> " + txt
|
1734
1751
|
ET.register_namespace("D", "DAV:")
|
1735
1752
|
xroot = mkenod("D:orz")
|
1736
1753
|
xroot.insert(0, parse_xml(txt))
|
@@ -1788,6 +1805,7 @@ class HttpCli(object):
|
|
1788
1805
|
uenc = enc.upper()
|
1789
1806
|
|
1790
1807
|
txt = buf.decode(enc, "replace")
|
1808
|
+
self.hint = "<xml> " + txt
|
1791
1809
|
ET.register_namespace("D", "DAV:")
|
1792
1810
|
lk = parse_xml(txt)
|
1793
1811
|
assert lk.tag == "{DAV:}lockinfo"
|
@@ -3995,6 +4013,13 @@ class HttpCli(object):
|
|
3995
4013
|
if not editions:
|
3996
4014
|
return self.tx_404()
|
3997
4015
|
|
4016
|
+
#
|
4017
|
+
# force download
|
4018
|
+
|
4019
|
+
if "dl" in self.ouparam:
|
4020
|
+
cdis = gen_content_disposition(os.path.basename(req_path))
|
4021
|
+
self.out_headers["Content-Disposition"] = cdis
|
4022
|
+
|
3998
4023
|
#
|
3999
4024
|
# if-modified
|
4000
4025
|
|
@@ -4162,6 +4187,13 @@ class HttpCli(object):
|
|
4162
4187
|
if not editions:
|
4163
4188
|
return self.tx_404()
|
4164
4189
|
|
4190
|
+
#
|
4191
|
+
# force download
|
4192
|
+
|
4193
|
+
if "dl" in self.ouparam:
|
4194
|
+
cdis = gen_content_disposition(os.path.basename(req_path))
|
4195
|
+
self.out_headers["Content-Disposition"] = cdis
|
4196
|
+
|
4165
4197
|
#
|
4166
4198
|
# if-modified
|
4167
4199
|
|
@@ -4707,24 +4739,7 @@ class HttpCli(object):
|
|
4707
4739
|
if maxn < nf:
|
4708
4740
|
raise Pebkac(400, t)
|
4709
4741
|
|
4710
|
-
|
4711
|
-
afn = "".join([x if x in safe.replace('"', "") else "_" for x in fn])
|
4712
|
-
bascii = unicode(safe).encode("utf-8")
|
4713
|
-
zb = fn.encode("utf-8", "xmlcharrefreplace")
|
4714
|
-
if not PY2:
|
4715
|
-
zbl = [
|
4716
|
-
chr(x).encode("utf-8")
|
4717
|
-
if x in bascii
|
4718
|
-
else "%{:02x}".format(x).encode("ascii")
|
4719
|
-
for x in zb
|
4720
|
-
]
|
4721
|
-
else:
|
4722
|
-
zbl = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in zb]
|
4723
|
-
|
4724
|
-
ufn = b"".join(zbl).decode("ascii")
|
4725
|
-
|
4726
|
-
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
|
4727
|
-
cdis = cdis.format(afn, ext, ufn, ext)
|
4742
|
+
cdis = gen_content_disposition("%s.%s" % (fn, ext))
|
4728
4743
|
self.log(repr(cdis))
|
4729
4744
|
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
|
4730
4745
|
|
@@ -4911,7 +4926,8 @@ class HttpCli(object):
|
|
4911
4926
|
"lastmod": int(ts_md * 1000),
|
4912
4927
|
"lang": self.args.lang,
|
4913
4928
|
"favico": self.args.favico,
|
4914
|
-
"have_emp": self.args.emp,
|
4929
|
+
"have_emp": int(self.args.emp),
|
4930
|
+
"md_no_br": int(vn.flags.get("md_no_br") or 0),
|
4915
4931
|
"md_chk_rate": self.args.mcr,
|
4916
4932
|
"md": boundary,
|
4917
4933
|
"arg_base": arg_base,
|
copyparty/mdns.py
CHANGED
@@ -27,7 +27,7 @@ from .stolen.dnslib import (
|
|
27
27
|
DNSRecord,
|
28
28
|
set_avahi_379,
|
29
29
|
)
|
30
|
-
from .util import CachedSet, Daemon, Netdev, list_ips, min_ex
|
30
|
+
from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex
|
31
31
|
|
32
32
|
if TYPE_CHECKING:
|
33
33
|
from .svchub import SvcHub
|
@@ -371,7 +371,7 @@ class MDNS(MCast):
|
|
371
371
|
cip = addr[0]
|
372
372
|
v6 = ":" in cip
|
373
373
|
if (cip.startswith("169.254") and not self.ll_ok) or (
|
374
|
-
v6 and not cip.startswith(
|
374
|
+
v6 and not cip.startswith(IP6_LL)
|
375
375
|
):
|
376
376
|
return
|
377
377
|
|
copyparty/multicast.py
CHANGED
@@ -15,7 +15,7 @@ from ipaddress import (
|
|
15
15
|
)
|
16
16
|
|
17
17
|
from .__init__ import MACOS, TYPE_CHECKING
|
18
|
-
from .util import Daemon, Netdev, find_prefix, min_ex, spack
|
18
|
+
from .util import IP6_LL, IP64_LL, Daemon, Netdev, find_prefix, min_ex, spack
|
19
19
|
|
20
20
|
if TYPE_CHECKING:
|
21
21
|
from .svchub import SvcHub
|
@@ -142,7 +142,7 @@ class MCast(object):
|
|
142
142
|
all_selected = ips[:]
|
143
143
|
|
144
144
|
# discard non-linklocal ipv6
|
145
|
-
ips = [x for x in ips if ":" not in x or x.startswith(
|
145
|
+
ips = [x for x in ips if ":" not in x or x.startswith(IP6_LL)]
|
146
146
|
|
147
147
|
if not ips:
|
148
148
|
raise NoIPs()
|
@@ -180,7 +180,7 @@ class MCast(object):
|
|
180
180
|
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
|
181
181
|
|
182
182
|
# gvfs breaks if a linklocal ip appears in a dns reply
|
183
|
-
ll = {k: v for k, v in srv.ips.items() if k.startswith(
|
183
|
+
ll = {k: v for k, v in srv.ips.items() if k.startswith(IP64_LL)}
|
184
184
|
rt = {k: v for k, v in srv.ips.items() if k not in ll}
|
185
185
|
|
186
186
|
if self.args.ll or not rt:
|
copyparty/pwhash.py
CHANGED
copyparty/smbd.py
CHANGED
@@ -315,7 +315,7 @@ class SMB(object):
|
|
315
315
|
t = "blocked rename (no-move-acc %s): /%s @%s"
|
316
316
|
yeet(t % (vfs1.axs.umove, vp1, uname))
|
317
317
|
|
318
|
-
self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
|
318
|
+
self.hub.up2k.handle_mv("", uname, "1.7.6.2", vp1, vp2)
|
319
319
|
try:
|
320
320
|
bos.makedirs(ap2, vf=vfs2.flags)
|
321
321
|
except:
|
copyparty/svchub.py
CHANGED
@@ -128,6 +128,7 @@ class SvcHub(object):
|
|
128
128
|
self.nsigs = 3
|
129
129
|
self.retcode = 0
|
130
130
|
self.httpsrv_up = 0
|
131
|
+
self.qr_tsz = None
|
131
132
|
|
132
133
|
self.log_mutex = threading.Lock()
|
133
134
|
self.cday = 0
|
@@ -319,7 +320,7 @@ class SvcHub(object):
|
|
319
320
|
|
320
321
|
self._feature_test()
|
321
322
|
|
322
|
-
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
323
|
+
decs = {k.strip(): 1 for k in self.args.th_dec.split(",")}
|
323
324
|
if not HAVE_VIPS:
|
324
325
|
decs.pop("vips", None)
|
325
326
|
if not HAVE_PIL:
|
@@ -777,7 +778,27 @@ class SvcHub(object):
|
|
777
778
|
self.signal_handler(signal.SIGTERM, None)
|
778
779
|
|
779
780
|
def sticky_qr(self) :
|
780
|
-
|
781
|
+
self._sticky_qr()
|
782
|
+
|
783
|
+
def _unsticky_qr(self, flush=True) :
|
784
|
+
print("\033[s\033[J\033[r\033[u", file=sys.stderr, end="")
|
785
|
+
if flush:
|
786
|
+
sys.stderr.flush()
|
787
|
+
|
788
|
+
def _sticky_qr(self, force = False) :
|
789
|
+
sz = termsize()
|
790
|
+
if self.qr_tsz == sz:
|
791
|
+
if not force:
|
792
|
+
return
|
793
|
+
else:
|
794
|
+
force = False
|
795
|
+
|
796
|
+
if self.qr_tsz:
|
797
|
+
self._unsticky_qr(False)
|
798
|
+
else:
|
799
|
+
atexit.register(self._unsticky_qr)
|
800
|
+
|
801
|
+
tw, th = self.qr_tsz = sz
|
781
802
|
zs1, qr = self.tcpsrv.qr.split("\n", 1)
|
782
803
|
url, colr = zs1.split(" ", 1)
|
783
804
|
nl = len(qr.split("\n")) # numlines
|
@@ -801,17 +822,34 @@ class SvcHub(object):
|
|
801
822
|
url = "%s\033[%d;%dH%s\033[0m" % (colr, sh + 1, (nl + lp) * 2, url)
|
802
823
|
qr = colr + qr
|
803
824
|
|
804
|
-
def unlock():
|
805
|
-
print("\033[s\033[r\033[u", file=sys.stderr)
|
806
|
-
|
807
|
-
atexit.register(unlock)
|
808
825
|
t = "%s\033[%dA" % ("\n" * nl, nl)
|
809
826
|
t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url)
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
827
|
+
if not force:
|
828
|
+
self.log("qr", "sticky-qrcode %sx%s,%s" % (tw, th, sh), 6)
|
829
|
+
self.pr(t, file=sys.stderr, end="")
|
830
|
+
|
831
|
+
def _qr_thr(self):
|
832
|
+
qr = self.tcpsrv.qr
|
833
|
+
w8 = self.args.qr_wait
|
834
|
+
if w8:
|
835
|
+
time.sleep(w8)
|
836
|
+
self.log("qr-code", qr)
|
837
|
+
w8 = self.args.qr_every
|
838
|
+
msg = "%s\033[%dA" % (qr, len(qr.split("\n")))
|
839
|
+
while w8:
|
840
|
+
time.sleep(w8)
|
841
|
+
if self.stopping:
|
842
|
+
break
|
843
|
+
if self.args.qr_pin:
|
844
|
+
self._sticky_qr(True)
|
845
|
+
else:
|
846
|
+
self.log("qr-code", msg)
|
847
|
+
w8 = self.args.qr_winch
|
848
|
+
while w8:
|
849
|
+
time.sleep(w8)
|
850
|
+
if self.stopping:
|
851
|
+
break
|
852
|
+
self._sticky_qr()
|
815
853
|
|
816
854
|
def cb_httpsrv_up(self) :
|
817
855
|
self.httpsrv_up += 1
|
@@ -827,11 +865,10 @@ class SvcHub(object):
|
|
827
865
|
if self.tcpsrv.qr:
|
828
866
|
if self.args.qr_pin:
|
829
867
|
self.sticky_qr()
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
self.log("qr-code", self.tcpsrv.qr)
|
868
|
+
if self.args.qr_wait or self.args.qr_every or self.args.qr_winch:
|
869
|
+
Daemon(self._qr_thr, "qr")
|
870
|
+
elif not self.args.qr_pin:
|
871
|
+
self.log("qr-code", self.tcpsrv.qr)
|
835
872
|
else:
|
836
873
|
self.log("root", "workers OK\n")
|
837
874
|
|
@@ -1085,7 +1122,7 @@ class SvcHub(object):
|
|
1085
1122
|
al.tcolor = "".join([x * 2 for x in al.tcolor])
|
1086
1123
|
|
1087
1124
|
zs = al.u2sz
|
1088
|
-
zsl = zs.split(",")
|
1125
|
+
zsl = [x.strip() for x in zs.split(",")]
|
1089
1126
|
if len(zsl) not in (1, 3):
|
1090
1127
|
t = "invalid --u2sz; must be either one number, or a comma-separated list of three numbers (min,default,max)"
|
1091
1128
|
raise Exception(t)
|
copyparty/tcpsrv.py
CHANGED
@@ -16,6 +16,7 @@ from .util import (
|
|
16
16
|
E_ADDR_NOT_AVAIL,
|
17
17
|
E_UNREACH,
|
18
18
|
HAVE_IPV6,
|
19
|
+
IP6_LL,
|
19
20
|
IP6ALL,
|
20
21
|
VF_CAREFUL,
|
21
22
|
Netdev,
|
@@ -137,12 +138,12 @@ class TcpSrv(object):
|
|
137
138
|
# keep IPv6 LL-only nics
|
138
139
|
ll_ok = set()
|
139
140
|
for ip, nd in self.netdevs.items():
|
140
|
-
if not ip.startswith(
|
141
|
+
if not ip.startswith(IP6_LL):
|
141
142
|
continue
|
142
143
|
|
143
144
|
just_ll = True
|
144
145
|
for ip2, nd2 in self.netdevs.items():
|
145
|
-
if nd == nd2 and ":" in ip2 and not ip2.startswith(
|
146
|
+
if nd == nd2 and ":" in ip2 and not ip2.startswith(IP6_LL):
|
146
147
|
just_ll = False
|
147
148
|
|
148
149
|
if just_ll or self.args.ll:
|
@@ -161,7 +162,7 @@ class TcpSrv(object):
|
|
161
162
|
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
|
162
163
|
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
163
164
|
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
164
|
-
if ip.startswith(
|
165
|
+
if ip.startswith(IP6_LL) and ip not in ll_ok:
|
165
166
|
continue
|
166
167
|
|
167
168
|
for port in sorted(self.args.p):
|
copyparty/up2k.py
CHANGED
@@ -88,6 +88,9 @@ ICV_EXTS = set(zsg.split(","))
|
|
88
88
|
zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
|
89
89
|
VCV_EXTS = set(zsg.split(","))
|
90
90
|
|
91
|
+
zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv"
|
92
|
+
ACV_EXTS = set(zsg.split(","))
|
93
|
+
|
91
94
|
zsg = "nohash noidx xdev xvol"
|
92
95
|
VF_AFFECTS_INDEXING = set(zsg.split(" "))
|
93
96
|
|
@@ -918,6 +921,12 @@ class Up2k(object):
|
|
918
921
|
with self.mutex, self.reg_mutex:
|
919
922
|
# only need to protect register_vpath but all in one go feels right
|
920
923
|
for vol in vols:
|
924
|
+
if bos.path.isfile(vol.realpath):
|
925
|
+
self.volstate[vol.vpath] = "online (just-a-file)"
|
926
|
+
t = "NOTE: volume [/%s] is a file, not a folder"
|
927
|
+
self.log(t % (vol.vpath,))
|
928
|
+
continue
|
929
|
+
|
921
930
|
try:
|
922
931
|
# mkdir gonna happen at snap anyways;
|
923
932
|
bos.makedirs(vol.realpath, vf=vol.flags)
|
@@ -1475,7 +1484,7 @@ class Up2k(object):
|
|
1475
1484
|
unreg = []
|
1476
1485
|
files = []
|
1477
1486
|
fat32 = True
|
1478
|
-
cv = vcv = ""
|
1487
|
+
cv = vcv = acv = ""
|
1479
1488
|
|
1480
1489
|
th_cvd = self.args.th_coversd
|
1481
1490
|
th_cvds = self.args.th_coversd_set
|
@@ -1584,9 +1593,11 @@ class Up2k(object):
|
|
1584
1593
|
cv = iname
|
1585
1594
|
elif not vcv and ext in VCV_EXTS and not iname.startswith("."):
|
1586
1595
|
vcv = iname
|
1596
|
+
elif not acv and ext in ACV_EXTS and not iname.startswith("."):
|
1597
|
+
acv = iname
|
1587
1598
|
|
1588
1599
|
if not cv:
|
1589
|
-
cv = vcv
|
1600
|
+
cv = vcv or acv
|
1590
1601
|
|
1591
1602
|
if not self.args.no_dirsz:
|
1592
1603
|
tnf += len(files)
|
copyparty/util.py
CHANGED
@@ -52,6 +52,7 @@ from .__init__ import (
|
|
52
52
|
VT100,
|
53
53
|
WINDOWS,
|
54
54
|
EnvParams,
|
55
|
+
unicode,
|
55
56
|
)
|
56
57
|
from .__version__ import S_BUILD_DT, S_VERSION
|
57
58
|
from .stolen import surrogateescape
|
@@ -112,7 +113,13 @@ E_ACCESS = _ens("EACCES WSAEACCES")
|
|
112
113
|
E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH")
|
113
114
|
|
114
115
|
IP6ALL = "0:0:0:0:0:0:0:0"
|
116
|
+
IP6_LL = ("fe8", "fe9", "fea", "feb")
|
117
|
+
IP64_LL = ("fe8", "fe9", "fea", "feb", "169.254")
|
115
118
|
|
119
|
+
UC_CDISP = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._"
|
120
|
+
BC_CDISP = UC_CDISP.encode("ascii")
|
121
|
+
UC_CDISP_SET = set(UC_CDISP)
|
122
|
+
BC_CDISP_SET = set(BC_CDISP)
|
116
123
|
|
117
124
|
try:
|
118
125
|
import fcntl
|
@@ -1985,6 +1992,29 @@ def gencookie(
|
|
1985
1992
|
)
|
1986
1993
|
|
1987
1994
|
|
1995
|
+
def gen_content_disposition(fn ) :
|
1996
|
+
safe = UC_CDISP_SET
|
1997
|
+
bsafe = BC_CDISP_SET
|
1998
|
+
fn = fn.replace("/", "_").replace("\\", "_")
|
1999
|
+
zb = fn.encode("utf-8", "xmlcharrefreplace")
|
2000
|
+
if not PY2:
|
2001
|
+
zbl = [
|
2002
|
+
chr(x).encode("utf-8")
|
2003
|
+
if x in bsafe
|
2004
|
+
else "%{:02X}".format(x).encode("ascii")
|
2005
|
+
for x in zb
|
2006
|
+
]
|
2007
|
+
else:
|
2008
|
+
zbl = [unicode(x) if x in bsafe else "%{:02X}".format(ord(x)) for x in zb]
|
2009
|
+
|
2010
|
+
ufn = b"".join(zbl).decode("ascii")
|
2011
|
+
afn = "".join([x if x in safe else "_" for x in fn]).lstrip(".")
|
2012
|
+
while ".." in afn:
|
2013
|
+
afn = afn.replace("..", ".")
|
2014
|
+
|
2015
|
+
return "attachment; filename=\"%s\"; filename*=UTF-8''%s" % (afn, ufn)
|
2016
|
+
|
2017
|
+
|
1988
2018
|
def humansize(sz , terse = False) :
|
1989
2019
|
for unit in HUMANSIZE_UNITS:
|
1990
2020
|
if sz < 1024:
|
copyparty/web/a/u2c.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
S_VERSION = "2.
|
5
|
-
S_BUILD_DT = "2025-
|
4
|
+
S_VERSION = "2.12"
|
5
|
+
S_BUILD_DT = "2025-08-26"
|
6
6
|
|
7
7
|
"""
|
8
8
|
u2c.py: upload to copyparty
|
@@ -10,7 +10,7 @@ u2c.py: upload to copyparty
|
|
10
10
|
https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py
|
11
11
|
|
12
12
|
- dependencies: no
|
13
|
-
- supports python 2.6, 2.7, and 3.3 through 3.
|
13
|
+
- supports python 2.6, 2.7, and 3.3 through 3.14
|
14
14
|
- if something breaks just try again and it'll autoresume
|
15
15
|
"""
|
16
16
|
|
@@ -675,7 +675,7 @@ def walkdirs(err, tops, excl):
|
|
675
675
|
yield stop, ap[len(stop) :].lstrip(sep), inf
|
676
676
|
else:
|
677
677
|
d, n = top.rsplit(sep, 1)
|
678
|
-
yield d, n, os.stat(top)
|
678
|
+
yield d or b"/", n, os.stat(top)
|
679
679
|
|
680
680
|
|
681
681
|
# mostly from copyparty/util.py
|
@@ -1525,10 +1525,10 @@ def main():
|
|
1525
1525
|
|
1526
1526
|
# fmt: off
|
1527
1527
|
ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool " + ver, epilog="""
|
1528
|
-
NOTE:
|
1529
|
-
source file/folder selection uses rsync syntax, meaning that:
|
1528
|
+
NOTE: source file/folder selection uses rsync syntax, meaning that:
|
1530
1529
|
"foo" uploads the entire folder to URL/foo/
|
1531
1530
|
"foo/" uploads the CONTENTS of the folder into URL/
|
1531
|
+
NOTE: if server has --usernames enabled, then password is "username:password"
|
1532
1532
|
""")
|
1533
1533
|
|
1534
1534
|
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
copyparty/web/browser.js.gz
CHANGED
Binary file
|
copyparty/web/md.html
CHANGED
@@ -130,7 +130,8 @@ write markdown (most html is 🙆 too)
|
|
130
130
|
|
131
131
|
var SR = "{{ r }}",
|
132
132
|
last_modified = {{ lastmod }},
|
133
|
-
have_emp = {{
|
133
|
+
have_emp = {{ have_emp }},
|
134
|
+
md_no_br = {{ md_no_br }},
|
134
135
|
dfavico = "{{ favico }}";
|
135
136
|
|
136
137
|
var md_opt = {
|
copyparty/web/md.js.gz
CHANGED
Binary file
|
copyparty/web/md2.js.gz
CHANGED
Binary file
|
copyparty/web/mde.html
CHANGED
copyparty/web/splash.js.gz
CHANGED
Binary file
|
copyparty/web/ui.css.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.19.
|
3
|
+
Version: 1.19.6
|
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
|
@@ -637,6 +637,8 @@ for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mn
|
|
637
637
|
|
638
638
|
the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
|
639
639
|
|
640
|
+
> ℹ️ this also works for single files, because files can also be volumes
|
641
|
+
|
640
642
|
|
641
643
|
## dotfiles
|
642
644
|
|
@@ -1494,6 +1496,7 @@ and some minor issues,
|
|
1494
1496
|
* win10 onwards does not allow connecting anonymously / without accounts
|
1495
1497
|
* python3 only
|
1496
1498
|
* slow (the builtin webdav support in windows is 5x faster, and rclone-webdav is 30x faster)
|
1499
|
+
* those numbers are specifically for copyparty's smb-server (because it sucks); other smb-servers should be similar to webdav
|
1497
1500
|
|
1498
1501
|
known client bugs:
|
1499
1502
|
* on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2
|
@@ -2679,9 +2682,16 @@ NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/
|
|
2679
2682
|
|
2680
2683
|
the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
|
2681
2684
|
|
2685
|
+
if you want to sync with `u2c.py` then:
|
2686
|
+
* the `e2dsa` option (either globally or volflag) must be enabled on the server for the volumes you're syncing into
|
2687
|
+
* ...but DON'T enable global-options `no-hash` or `no-idx` (or volflags `nohash` / `noidx`), or at least make sure they are configured so they do not affect anything you are syncing into
|
2688
|
+
* ...and u2c needs the delete-permission, so either `rwd` at minimum, or just `A` which is the same as `rwmd.a`
|
2689
|
+
* quick reminder that `a` and `A` are different permissions, and `.` is very useful for sync
|
2690
|
+
|
2682
2691
|
alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
|
2683
2692
|
|
2684
2693
|
* starting from rclone v1.63, rclone is faster than u2c.py on low-latency connections
|
2694
|
+
* but this is only true for the initial upload; u2c will be faster for periodic syncing
|
2685
2695
|
|
2686
2696
|
|
2687
2697
|
## mount as drive
|
@@ -1,38 +1,38 @@
|
|
1
1
|
copyparty/__init__.py,sha256=SJtQjM-9PP9K-IaoM9M3iNKvRApp0omOrAN6YtXTPNM,2599
|
2
|
-
copyparty/__main__.py,sha256=
|
3
|
-
copyparty/__version__.py,sha256=
|
4
|
-
copyparty/authsrv.py,sha256=
|
2
|
+
copyparty/__main__.py,sha256=l9zhoDdwkKH33EJFoDoDfX2zpf-qjKwMWQVPuIkrmTg,139985
|
3
|
+
copyparty/__version__.py,sha256=UAYgCM1TKjFBQYsu5L3qncXYlq_Poy2878QCRjvhDME,251
|
4
|
+
copyparty/authsrv.py,sha256=yQHc2Lrl9yVg8bE_ZA6OukKLIS8wukm8Uvo6ANmOY8Q,126249
|
5
5
|
copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
|
6
6
|
copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
|
7
7
|
copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
|
8
|
-
copyparty/broker_util.py,sha256=
|
9
|
-
copyparty/cert.py,sha256=
|
10
|
-
copyparty/cfg.py,sha256=
|
8
|
+
copyparty/broker_util.py,sha256=oZ3dGdJXLYvmdV4KSFdUv_PGErkgU-7kgr1SIglkjCU,1663
|
9
|
+
copyparty/cert.py,sha256=OGTUBxhqPbseG0Bd4cHD6e5T5T8JdGqp3q0KAYqX0Cc,8031
|
10
|
+
copyparty/cfg.py,sha256=SBS3o0fQTnNy-hm97e0dzFJFcjmY8O-jiqZh6sJ3cyE,16204
|
11
11
|
copyparty/dxml.py,sha256=VZADJS9z18LalENSvVfjk29ddnpaDQ-v8AVm_THwS1c,2607
|
12
12
|
copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
|
13
|
-
copyparty/ftpd.py,sha256=
|
14
|
-
copyparty/httpcli.py,sha256=
|
13
|
+
copyparty/ftpd.py,sha256=QHyrtREOD6s3P9XghZKbq0nlE1KFbpcfLhQk2pIDXGw,18797
|
14
|
+
copyparty/httpcli.py,sha256=dFcDgywGgQV0AZ_LxB9Oc2N7sKrISWQIt43PuFigbdc,238501
|
15
15
|
copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
|
16
16
|
copyparty/httpsrv.py,sha256=12j76CpAlJEeZU17CbWLnoVqoAPdv4xN48prQtE0IRs,19051
|
17
17
|
copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
|
18
|
-
copyparty/mdns.py,sha256=
|
18
|
+
copyparty/mdns.py,sha256=usQLqWfFCz8Nqr5Z1x7dtxrnx_gORWpxAGAQ9tPMS_w,18413
|
19
19
|
copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
|
20
20
|
copyparty/mtag.py,sha256=yePCWUwiW7G90gd8AsqtDTcyRZ54wfu-ayLRu0i7Hys,22832
|
21
|
-
copyparty/multicast.py,sha256=
|
22
|
-
copyparty/pwhash.py,sha256=
|
23
|
-
copyparty/smbd.py,sha256=
|
21
|
+
copyparty/multicast.py,sha256=xjCBHbbFI7XEohXxgBCAeExkzXJede6TLc5oR7ov5vM,12322
|
22
|
+
copyparty/pwhash.py,sha256=58txP8GXIHOtWd__Tni8qkilHEGpOFsKIPuzjdFLc6M,4426
|
23
|
+
copyparty/smbd.py,sha256=x6Y0alKOoMb8GmO0HNqwvucFwUJW9ct1HKL2Bx_b6MY,14655
|
24
24
|
copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
|
25
25
|
copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
|
26
26
|
copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
|
27
|
-
copyparty/svchub.py,sha256=
|
27
|
+
copyparty/svchub.py,sha256=GC8yKpbnYzI-ubRH8JqC4vDXsAMGgaA_-_HOMv3kw0U,52434
|
28
28
|
copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
|
29
|
-
copyparty/tcpsrv.py,sha256=
|
29
|
+
copyparty/tcpsrv.py,sha256=6SH5ZxBXtQF8hHgReNbvKtqgTFgLFdpgFL_lXL1khqI,21925
|
30
30
|
copyparty/tftpd.py,sha256=fXW0w2_fSbzKVQGTLgr4DbtxaIEfwB591bl0pYhwy6k,14272
|
31
31
|
copyparty/th_cli.py,sha256=n3MMbnN7HTVSBHkTI2qLIjgwVvHkGsfTW7-aPJ-2o4s,5545
|
32
32
|
copyparty/th_srv.py,sha256=iAWBHgtAaYAshXdCFrUx7IWq4y3NXrH79yzHChsy2dc,38335
|
33
33
|
copyparty/u2idx.py,sha256=mTtUKyIv9putnkDel8cttqMlZXDxb3IAga8J7e0kHls,13666
|
34
|
-
copyparty/up2k.py,sha256=
|
35
|
-
copyparty/util.py,sha256=
|
34
|
+
copyparty/up2k.py,sha256=qVPVN6qKAT4H3eeWyirUpgF4D7yH6N-B6m-NhwdJlSw,181539
|
35
|
+
copyparty/util.py,sha256=CBSe4e4-vd2zaSfjXd4H_J0tDFFwEy-DI9OjvjpTf7Y,107866
|
36
36
|
copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
37
|
copyparty/bos/bos.py,sha256=DYt5mJJNt-935rU7HRm8kt_whpcVSI0uSphvD7PXrJo,2247
|
38
38
|
copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
|
@@ -57,18 +57,18 @@ copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8
|
|
57
57
|
copyparty/web/baguettebox.js.gz,sha256=VR-MdQ11CZ1PkMW0VTv36RPLY5L1w2rDj2CY-TPfvRM,8269
|
58
58
|
copyparty/web/browser.css.gz,sha256=8zQ0rWJvNPUOgOOY4EMCFh20q8S3-bDhiFi4sYx135M,15661
|
59
59
|
copyparty/web/browser.html,sha256=lhelkXI8_HGfuqo_5b6XEGzf8VNodOMXE9kbv31JtbM,4790
|
60
|
-
copyparty/web/browser.js.gz,sha256=
|
60
|
+
copyparty/web/browser.js.gz,sha256=kU3y_olayBkx_c8L07St0Ci9cKoLOy16xbLPaZHHDKQ,314574
|
61
61
|
copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
|
62
62
|
copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
|
63
63
|
copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
|
64
64
|
copyparty/web/idp.html,sha256=qOjjvZz6fVk0W8cXiP8fXeTp5KMa_dDV7BRzJNUModo,1481
|
65
65
|
copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
|
66
|
-
copyparty/web/md.html,sha256=
|
67
|
-
copyparty/web/md.js.gz,sha256=
|
66
|
+
copyparty/web/md.html,sha256=jJ-aE6vbQiWlksAPpkqDYWNnTo8tJW1RhEXrMMmcwRg,4205
|
67
|
+
copyparty/web/md.js.gz,sha256=moH1ul86OFDTfUKmN09aLVVul3RN-gjI2UxuZGcGjjI,4169
|
68
68
|
copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
|
69
|
-
copyparty/web/md2.js.gz,sha256=
|
69
|
+
copyparty/web/md2.js.gz,sha256=xiYIwvKscN4HxefrjkEvc2JtBUUQyEodbKDX5T26ev8,8407
|
70
70
|
copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
|
71
|
-
copyparty/web/mde.html,sha256=
|
71
|
+
copyparty/web/mde.html,sha256=JnvToXsrUHwPJUkrZvt6pitrficWjIAbfBofQAVgGyU,1775
|
72
72
|
copyparty/web/mde.js.gz,sha256=FQplRzzMJqC_xNb8gmzj79PrlCfcPdKIm85lM6cR-xA,2220
|
73
73
|
copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
|
74
74
|
copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
|
@@ -80,16 +80,16 @@ copyparty/web/shares.html,sha256=ZZ9BIuzhbVtJCAZOb_PAaEY_z9jo8i93QEJolNDHX3g,257
|
|
80
80
|
copyparty/web/shares.js.gz,sha256=QgtzZ6oKJqGVlAEbhVCn-35F6mnwLwOmndRqlgtJd74,942
|
81
81
|
copyparty/web/splash.css.gz,sha256=rumglcBttk2HVhAEoS65m2GZIQtXd4c9HOHvd82OJeg,1097
|
82
82
|
copyparty/web/splash.html,sha256=91KdrAfoBZgeM8qap5J-KeTVs_O4WvtwiZYpjVBTj1o,6884
|
83
|
-
copyparty/web/splash.js.gz,sha256=
|
83
|
+
copyparty/web/splash.js.gz,sha256=9h1yRUYteu7cVxcYWYG06A78YGXN5OS1AgKx3ELQg-8,15888
|
84
84
|
copyparty/web/svcs.html,sha256=wGW7bwY3EIDRpp9apzJsED_UMlHX31X2H3ZnN13tWCI,14913
|
85
85
|
copyparty/web/svcs.js.gz,sha256=_dvatVBMVI_iy5MnYQeZumFGAnu9lAP_WKrhOA1TYxs,852
|
86
|
-
copyparty/web/ui.css.gz,sha256=
|
86
|
+
copyparty/web/ui.css.gz,sha256=0NOzup4KnBwcKGV7MZJvHaglEvLti6tgwnY4o21wMTo,2848
|
87
87
|
copyparty/web/up2k.js.gz,sha256=pgj3drYXEiHOkSpcZp4ttWCqD72BpFp16cuHObl0zpo,24994
|
88
88
|
copyparty/web/util.js.gz,sha256=A28FamE9n03YkSE09G4IOLvFaDZK-j5Mi-xl124JcC8,15569
|
89
89
|
copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
|
90
90
|
copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
91
91
|
copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
|
92
|
-
copyparty/web/a/u2c.py,sha256=
|
92
|
+
copyparty/web/a/u2c.py,sha256=v7YDWBh_d9NbT_-zYZABjPTSqMBCG2naF8PaLZZ2W8o,53334
|
93
93
|
copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
|
94
94
|
copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
95
95
|
copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
|
@@ -105,9 +105,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
|
|
105
105
|
copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
|
106
106
|
copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
|
107
107
|
copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
|
108
|
-
copyparty-1.19.
|
109
|
-
copyparty-1.19.
|
110
|
-
copyparty-1.19.
|
111
|
-
copyparty-1.19.
|
112
|
-
copyparty-1.19.
|
113
|
-
copyparty-1.19.
|
108
|
+
copyparty-1.19.6.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
109
|
+
copyparty-1.19.6.dist-info/METADATA,sha256=1lwqgItGYrevYV6bWbGNj4KsFZfgqeKQmlfL5Km7meA,176818
|
110
|
+
copyparty-1.19.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
111
|
+
copyparty-1.19.6.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
112
|
+
copyparty-1.19.6.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
113
|
+
copyparty-1.19.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|