copyparty 1.11.1__py3-none-any.whl → 1.12.0__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 +0 -1
- copyparty/__main__.py +26 -31
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +41 -23
- copyparty/ftpd.py +1 -1
- copyparty/httpcli.py +112 -28
- copyparty/mtag.py +1 -2
- copyparty/star.py +4 -2
- copyparty/sutil.py +6 -1
- copyparty/svchub.py +26 -1
- copyparty/szip.py +5 -3
- copyparty/tftpd.py +3 -0
- copyparty/th_cli.py +1 -1
- copyparty/th_srv.py +48 -6
- copyparty/up2k.py +1 -1
- copyparty/util.py +18 -11
- copyparty/web/baguettebox.js.gz +0 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/busy.mp3.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/md.css.gz +0 -0
- 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.js.gz +0 -0
- copyparty/web/msg.css.gz +0 -0
- copyparty/web/splash.css.gz +0 -0
- copyparty/web/splash.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-1.11.1.dist-info → copyparty-1.12.0.dist-info}/METADATA +28 -7
- {copyparty-1.11.1.dist-info → copyparty-1.12.0.dist-info}/RECORD +39 -38
- {copyparty-1.11.1.dist-info → copyparty-1.12.0.dist-info}/LICENSE +0 -0
- {copyparty-1.11.1.dist-info → copyparty-1.12.0.dist-info}/WHEEL +0 -0
- {copyparty-1.11.1.dist-info → copyparty-1.12.0.dist-info}/entry_points.txt +0 -0
- {copyparty-1.11.1.dist-info → copyparty-1.12.0.dist-info}/top_level.txt +0 -0
copyparty/__init__.py
CHANGED
copyparty/__main__.py
CHANGED
@@ -151,7 +151,8 @@ def warn(msg ) :
|
|
151
151
|
|
152
152
|
|
153
153
|
def init_E(EE ) :
|
154
|
-
#
|
154
|
+
# some cpython alternatives (such as pyoxidizer) can
|
155
|
+
# __init__ several times, so do expensive stuff here
|
155
156
|
|
156
157
|
E = EE # pylint: disable=redefined-outer-name
|
157
158
|
|
@@ -184,34 +185,9 @@ def init_E(EE ) :
|
|
184
185
|
|
185
186
|
raise Exception("could not find a writable path for config")
|
186
187
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
import tempfile
|
191
|
-
from importlib.resources import open_binary
|
192
|
-
|
193
|
-
td = tempfile.TemporaryDirectory(prefix="")
|
194
|
-
atexit.register(td.cleanup)
|
195
|
-
tdn = td.name
|
196
|
-
|
197
|
-
with open_binary("copyparty", "z.tar") as tgz:
|
198
|
-
with tarfile.open(fileobj=tgz) as tf:
|
199
|
-
try:
|
200
|
-
tf.extractall(tdn, filter="tar")
|
201
|
-
except TypeError:
|
202
|
-
tf.extractall(tdn) # nosec (archive is safe)
|
203
|
-
|
204
|
-
return tdn
|
205
|
-
|
206
|
-
try:
|
207
|
-
E.mod = os.path.dirname(os.path.realpath(__file__))
|
208
|
-
if E.mod.endswith("__init__"):
|
209
|
-
E.mod = os.path.dirname(E.mod)
|
210
|
-
except:
|
211
|
-
if not E.ox:
|
212
|
-
raise
|
213
|
-
|
214
|
-
E.mod = _unpack()
|
188
|
+
E.mod = os.path.dirname(os.path.realpath(__file__))
|
189
|
+
if E.mod.endswith("__init__"):
|
190
|
+
E.mod = os.path.dirname(E.mod)
|
215
191
|
|
216
192
|
if sys.platform == "win32":
|
217
193
|
bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
|
@@ -268,6 +244,19 @@ def get_fk_salt() :
|
|
268
244
|
return ret.decode("utf-8")
|
269
245
|
|
270
246
|
|
247
|
+
def get_dk_salt() :
|
248
|
+
fp = os.path.join(E.cfg, "dk-salt.txt")
|
249
|
+
try:
|
250
|
+
with open(fp, "rb") as f:
|
251
|
+
ret = f.read().strip()
|
252
|
+
except:
|
253
|
+
ret = base64.b64encode(os.urandom(30))
|
254
|
+
with open(fp, "wb") as f:
|
255
|
+
f.write(ret + b"\n")
|
256
|
+
|
257
|
+
return ret.decode("utf-8")
|
258
|
+
|
259
|
+
|
271
260
|
def get_ah_salt() :
|
272
261
|
fp = os.path.join(E.cfg, "ah-salt.txt")
|
273
262
|
try:
|
@@ -863,6 +852,7 @@ def add_fs(ap):
|
|
863
852
|
ap2 = ap.add_argument_group("filesystem options")
|
864
853
|
rm_re_def = "5/0.1" if ANYWIN else "0/0"
|
865
854
|
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)")
|
855
|
+
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)")
|
866
856
|
|
867
857
|
|
868
858
|
def add_upload(ap):
|
@@ -910,6 +900,7 @@ def add_network(ap):
|
|
910
900
|
ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
|
911
901
|
ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
|
912
902
|
ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=186, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
|
903
|
+
ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
|
913
904
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
914
905
|
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
|
915
906
|
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
|
@@ -1123,13 +1114,14 @@ def add_safety(ap):
|
|
1123
1114
|
ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)")
|
1124
1115
|
|
1125
1116
|
|
1126
|
-
def add_salt(ap, fk_salt, ah_salt):
|
1117
|
+
def add_salt(ap, fk_salt, dk_salt, ah_salt):
|
1127
1118
|
ap2 = ap.add_argument_group('salting options')
|
1128
1119
|
ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)")
|
1129
1120
|
ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)")
|
1130
1121
|
ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]")
|
1131
1122
|
ap2.add_argument("--ah-cli", action="store_true", help="launch an interactive shell which hashes passwords without ever storing or displaying the original passwords")
|
1132
1123
|
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
|
1124
|
+
ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission")
|
1133
1125
|
ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
|
1134
1126
|
|
1135
1127
|
|
@@ -1195,6 +1187,8 @@ def add_thumbnail(ap):
|
|
1195
1187
|
|
1196
1188
|
def add_transcoding(ap):
|
1197
1189
|
ap2 = ap.add_argument_group('transcoding options')
|
1190
|
+
ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
|
1191
|
+
ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
|
1198
1192
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
1199
1193
|
ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)")
|
1200
1194
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
|
@@ -1311,6 +1305,7 @@ def run_argparse(
|
|
1311
1305
|
cert_path = os.path.join(E.cfg, "cert.pem")
|
1312
1306
|
|
1313
1307
|
fk_salt = get_fk_salt()
|
1308
|
+
dk_salt = get_dk_salt()
|
1314
1309
|
ah_salt = get_ah_salt()
|
1315
1310
|
|
1316
1311
|
# alpine peaks at 5 threads for some reason,
|
@@ -1342,7 +1337,7 @@ def run_argparse(
|
|
1342
1337
|
add_tftp(ap)
|
1343
1338
|
add_smb(ap)
|
1344
1339
|
add_safety(ap)
|
1345
|
-
add_salt(ap, fk_salt, ah_salt)
|
1340
|
+
add_salt(ap, fk_salt, dk_salt, ah_salt)
|
1346
1341
|
add_optouts(ap)
|
1347
1342
|
add_shutdown(ap)
|
1348
1343
|
add_yolo(ap)
|
copyparty/__version__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2024,
|
3
|
+
VERSION = (1, 12, 0)
|
4
|
+
CODENAME = "locksmith"
|
5
|
+
BUILD_DT = (2024, 4, 6)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
copyparty/authsrv.py
CHANGED
@@ -548,7 +548,12 @@ class VFS(object):
|
|
548
548
|
# no vfs nodes in the list of real inodes
|
549
549
|
real = [x for x in real if x[0] not in self.nodes]
|
550
550
|
|
551
|
+
dbv = self.dbv or self
|
551
552
|
for name, vn2 in sorted(self.nodes.items()):
|
553
|
+
if vn2.dbv == dbv and self.flags.get("dk"):
|
554
|
+
virt_vis[name] = vn2
|
555
|
+
continue
|
556
|
+
|
552
557
|
ok = False
|
553
558
|
zx = vn2.axs
|
554
559
|
axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
|
@@ -1217,7 +1222,9 @@ class AuthSrv(object):
|
|
1217
1222
|
if un.startswith("@"):
|
1218
1223
|
grp = un[1:]
|
1219
1224
|
uns = [x[0] for x in un_gns.items() if grp in x[1]]
|
1220
|
-
if
|
1225
|
+
if grp == "${g}":
|
1226
|
+
unames.append(un)
|
1227
|
+
elif not uns and not self.args.idp_h_grp:
|
1221
1228
|
t = "group [%s] must be defined with --grp argument (or in a [groups] config section)"
|
1222
1229
|
raise CfgEx(t % (grp,))
|
1223
1230
|
|
@@ -1227,31 +1234,28 @@ class AuthSrv(object):
|
|
1227
1234
|
|
1228
1235
|
# unames may still contain ${u} and ${g} so now expand those;
|
1229
1236
|
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
|
1230
|
-
if "*" not in un_gns:
|
1231
|
-
# need ("*","") to match "*" in unames
|
1232
|
-
un_gn.append(("*", ""))
|
1233
|
-
|
1234
|
-
for _, dst, vu, vg in vols:
|
1235
|
-
unames2 = set()
|
1236
|
-
for un, gn in un_gn:
|
1237
|
-
# if vu/vg (volume user/group) is non-null,
|
1238
|
-
# then each non-null value corresponds to
|
1239
|
-
# ${u}/${g}; consider this a filter to
|
1240
|
-
# apply to unames, as well as un_gn
|
1241
|
-
if (vu and vu != un) or (vg and vg != gn):
|
1242
|
-
continue
|
1243
1237
|
|
1244
|
-
|
1245
|
-
|
1246
|
-
uname = vu or un
|
1247
|
-
elif uname in ("${g}", "@${g}"):
|
1248
|
-
uname = vg or gn
|
1238
|
+
for src, dst, vu, vg in vols:
|
1239
|
+
unames2 = set(unames)
|
1249
1240
|
|
1250
|
-
|
1251
|
-
|
1241
|
+
if "${u}" in unames:
|
1242
|
+
if not vu:
|
1243
|
+
t = "cannot use ${u} in accs of volume [%s] because the volume url does not contain ${u}"
|
1244
|
+
raise CfgEx(t % (src,))
|
1245
|
+
unames2.add(vu)
|
1246
|
+
|
1247
|
+
if "@${g}" in unames:
|
1248
|
+
if not vg:
|
1249
|
+
t = "cannot use @${g} in accs of volume [%s] because the volume url does not contain @${g}"
|
1250
|
+
raise CfgEx(t % (src,))
|
1251
|
+
unames2.update([un for un, gn in un_gn if gn == vg])
|
1252
1252
|
|
1253
|
-
|
1254
|
-
|
1253
|
+
if "${g}" in unames:
|
1254
|
+
t = 'the accs of volume [%s] contains "${g}" but the only supported way of specifying that is "@${g}"'
|
1255
|
+
raise CfgEx(t % (src,))
|
1256
|
+
|
1257
|
+
unames2.discard("${u}")
|
1258
|
+
unames2.discard("@${g}")
|
1255
1259
|
|
1256
1260
|
self._read_vol_str(lvl, list(unames2), axs[dst])
|
1257
1261
|
|
@@ -1675,6 +1679,20 @@ class AuthSrv(object):
|
|
1675
1679
|
vol.flags["fk"] = int(fk) if fk is not True else 8
|
1676
1680
|
have_fk = True
|
1677
1681
|
|
1682
|
+
dk = vol.flags.get("dk")
|
1683
|
+
dks = vol.flags.get("dks")
|
1684
|
+
dky = vol.flags.get("dky")
|
1685
|
+
if dks is not None and dky is not None:
|
1686
|
+
t = "WARNING: volume /%s has both dks and dky enabled; this is too yolo and not permitted"
|
1687
|
+
raise Exception(t % (vol.vpath,))
|
1688
|
+
|
1689
|
+
if dks and not dk:
|
1690
|
+
dk = dks
|
1691
|
+
if dky and not dk:
|
1692
|
+
dk = dky
|
1693
|
+
if dk:
|
1694
|
+
vol.flags["dk"] = int(dk) if dk is not True else 8
|
1695
|
+
|
1678
1696
|
if have_fk and re.match(r"^[0-9\.]+$", self.args.fk_salt):
|
1679
1697
|
self.log("filekey salt: {}".format(self.args.fk_salt))
|
1680
1698
|
|
copyparty/ftpd.py
CHANGED
@@ -213,7 +213,7 @@ class FtpFs(AbstractedFS):
|
|
213
213
|
raise FSE("Cannot open existing file for writing")
|
214
214
|
|
215
215
|
self.validpath(ap)
|
216
|
-
return open(fsenc(ap), mode)
|
216
|
+
return open(fsenc(ap), mode, self.args.iobuf)
|
217
217
|
|
218
218
|
def chdir(self, path ) :
|
219
219
|
nwd = join(self.cwd, path)
|
copyparty/httpcli.py
CHANGED
@@ -36,6 +36,7 @@ from .bos import bos
|
|
36
36
|
from .star import StreamTar
|
37
37
|
from .sutil import StreamArc, gfilter
|
38
38
|
from .szip import StreamZip
|
39
|
+
from .util import unquote # type: ignore
|
39
40
|
from .util import (
|
40
41
|
APPLESAN_RE,
|
41
42
|
BITNESS,
|
@@ -84,7 +85,6 @@ from .util import (
|
|
84
85
|
sendfile_py,
|
85
86
|
undot,
|
86
87
|
unescape_cookie,
|
87
|
-
unquote, # type: ignore
|
88
88
|
unquotep,
|
89
89
|
vjoin,
|
90
90
|
vol_san,
|
@@ -170,7 +170,6 @@ class HttpCli(object):
|
|
170
170
|
self.parser = None
|
171
171
|
# end placeholders
|
172
172
|
|
173
|
-
self.bufsz = 1024 * 32
|
174
173
|
self.html_head = ""
|
175
174
|
|
176
175
|
def log(self, msg , c = 0) :
|
@@ -1607,15 +1606,16 @@ class HttpCli(object):
|
|
1607
1606
|
return enc or "utf-8"
|
1608
1607
|
|
1609
1608
|
def get_body_reader(self) :
|
1609
|
+
bufsz = self.args.s_rd_sz
|
1610
1610
|
if "chunked" in self.headers.get("transfer-encoding", "").lower():
|
1611
|
-
return read_socket_chunked(self.sr), -1
|
1611
|
+
return read_socket_chunked(self.sr, bufsz), -1
|
1612
1612
|
|
1613
1613
|
remains = int(self.headers.get("content-length", -1))
|
1614
1614
|
if remains == -1:
|
1615
1615
|
self.keepalive = False
|
1616
|
-
return read_socket_unbounded(self.sr), remains
|
1616
|
+
return read_socket_unbounded(self.sr, bufsz), remains
|
1617
1617
|
else:
|
1618
|
-
return read_socket(self.sr, remains), remains
|
1618
|
+
return read_socket(self.sr, bufsz, remains), remains
|
1619
1619
|
|
1620
1620
|
def dump_to_file(self, is_put ) :
|
1621
1621
|
# post_sz, sha_hex, sha_b64, remains, path, url
|
@@ -1637,7 +1637,7 @@ class HttpCli(object):
|
|
1637
1637
|
bos.makedirs(fdir)
|
1638
1638
|
|
1639
1639
|
open_ka = {"fun": open}
|
1640
|
-
open_a = ["wb",
|
1640
|
+
open_a = ["wb", self.args.iobuf]
|
1641
1641
|
|
1642
1642
|
# user-request || config-force
|
1643
1643
|
if ("gz" in vfs.flags or "xz" in vfs.flags) and (
|
@@ -1896,7 +1896,7 @@ class HttpCli(object):
|
|
1896
1896
|
f.seek(ofs)
|
1897
1897
|
with open(fp, "wb") as fo:
|
1898
1898
|
while nrem:
|
1899
|
-
buf = f.read(min(nrem,
|
1899
|
+
buf = f.read(min(nrem, self.args.iobuf))
|
1900
1900
|
if not buf:
|
1901
1901
|
break
|
1902
1902
|
|
@@ -1918,7 +1918,7 @@ class HttpCli(object):
|
|
1918
1918
|
return "%s %s n%s" % (spd1, spd2, self.conn.nreq)
|
1919
1919
|
|
1920
1920
|
def handle_post_multipart(self) :
|
1921
|
-
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
1921
|
+
self.parser = MultipartParser(self.log, self.args, self.sr, self.headers)
|
1922
1922
|
self.parser.parse()
|
1923
1923
|
|
1924
1924
|
file0 = []
|
@@ -1962,7 +1962,12 @@ class HttpCli(object):
|
|
1962
1962
|
|
1963
1963
|
v = self.uparam[k]
|
1964
1964
|
|
1965
|
-
|
1965
|
+
if self._use_dirkey():
|
1966
|
+
vn = self.vn
|
1967
|
+
rem = self.rem
|
1968
|
+
else:
|
1969
|
+
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
|
1970
|
+
|
1966
1971
|
zs = self.parser.require("files", 1024 * 1024)
|
1967
1972
|
if not zs:
|
1968
1973
|
raise Pebkac(422, "need files list")
|
@@ -2147,7 +2152,7 @@ class HttpCli(object):
|
|
2147
2152
|
|
2148
2153
|
self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains))
|
2149
2154
|
|
2150
|
-
reader = read_socket(self.sr, remains)
|
2155
|
+
reader = read_socket(self.sr, self.args.s_rd_sz, remains)
|
2151
2156
|
|
2152
2157
|
f = None
|
2153
2158
|
fpool = not self.args.no_fpool and sprs
|
@@ -2158,7 +2163,7 @@ class HttpCli(object):
|
|
2158
2163
|
except:
|
2159
2164
|
pass
|
2160
2165
|
|
2161
|
-
f = f or open(fsenc(path), "rb+",
|
2166
|
+
f = f or open(fsenc(path), "rb+", self.args.iobuf)
|
2162
2167
|
|
2163
2168
|
try:
|
2164
2169
|
f.seek(cstart[0])
|
@@ -2181,7 +2186,8 @@ class HttpCli(object):
|
|
2181
2186
|
)
|
2182
2187
|
ofs = 0
|
2183
2188
|
while ofs < chunksize:
|
2184
|
-
bufsz =
|
2189
|
+
bufsz = max(4 * 1024 * 1024, self.args.iobuf)
|
2190
|
+
bufsz = min(chunksize - ofs, bufsz)
|
2185
2191
|
f.seek(cstart[0] + ofs)
|
2186
2192
|
buf = f.read(bufsz)
|
2187
2193
|
for wofs in cstart[1:]:
|
@@ -2434,6 +2440,18 @@ class HttpCli(object):
|
|
2434
2440
|
suffix = "-{:.6f}-{}".format(time.time(), dip)
|
2435
2441
|
open_args = {"fdir": fdir, "suffix": suffix}
|
2436
2442
|
|
2443
|
+
if "replace" in self.uparam:
|
2444
|
+
abspath = os.path.join(fdir, fname)
|
2445
|
+
if not self.can_delete:
|
2446
|
+
self.log("user not allowed to overwrite with ?replace")
|
2447
|
+
elif bos.path.exists(abspath):
|
2448
|
+
try:
|
2449
|
+
wunlink(self.log, abspath, vfs.flags)
|
2450
|
+
t = "overwriting file with new upload: %s"
|
2451
|
+
except:
|
2452
|
+
t = "toctou while deleting for ?replace: %s"
|
2453
|
+
self.log(t % (abspath,))
|
2454
|
+
|
2437
2455
|
# reserve destination filename
|
2438
2456
|
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as zfw:
|
2439
2457
|
fname = zfw["orz"][1]
|
@@ -2478,7 +2496,7 @@ class HttpCli(object):
|
|
2478
2496
|
v2 = lim.dfv - lim.dfl
|
2479
2497
|
max_sz = min(v1, v2) if v1 and v2 else v1 or v2
|
2480
2498
|
|
2481
|
-
with ren_open(tnam, "wb",
|
2499
|
+
with ren_open(tnam, "wb", self.args.iobuf, **open_args) as zfw:
|
2482
2500
|
f, tnam = zfw["orz"]
|
2483
2501
|
tabspath = os.path.join(fdir, tnam)
|
2484
2502
|
self.log("writing to {}".format(tabspath))
|
@@ -2774,7 +2792,7 @@ class HttpCli(object):
|
|
2774
2792
|
if bos.path.exists(fp):
|
2775
2793
|
wunlink(self.log, fp, vfs.flags)
|
2776
2794
|
|
2777
|
-
with open(fsenc(fp), "wb",
|
2795
|
+
with open(fsenc(fp), "wb", self.args.iobuf) as f:
|
2778
2796
|
sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
|
2779
2797
|
|
2780
2798
|
if lim:
|
@@ -2853,6 +2871,30 @@ class HttpCli(object):
|
|
2853
2871
|
|
2854
2872
|
return file_lastmod, True
|
2855
2873
|
|
2874
|
+
def _use_dirkey(self, ap = "") :
|
2875
|
+
if self.can_read or not self.can_get:
|
2876
|
+
return False
|
2877
|
+
|
2878
|
+
if self.vn.flags.get("dky"):
|
2879
|
+
return True
|
2880
|
+
|
2881
|
+
req = self.uparam.get("k") or ""
|
2882
|
+
if not req:
|
2883
|
+
return False
|
2884
|
+
|
2885
|
+
dk_len = self.vn.flags.get("dk")
|
2886
|
+
if not dk_len:
|
2887
|
+
return False
|
2888
|
+
|
2889
|
+
ap = ap or self.vn.canonical(self.rem)
|
2890
|
+
zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_len]
|
2891
|
+
if req == zs:
|
2892
|
+
return True
|
2893
|
+
|
2894
|
+
t = "wrong dirkey, want %s, got %s\n vp: %s\n ap: %s"
|
2895
|
+
self.log(t % (zs, req, self.req, ap), 6)
|
2896
|
+
return False
|
2897
|
+
|
2856
2898
|
def _expand(self, txt , phs ) :
|
2857
2899
|
for ph in phs:
|
2858
2900
|
if ph.startswith("hdr."):
|
@@ -3006,8 +3048,7 @@ class HttpCli(object):
|
|
3006
3048
|
upper = gzip_orig_sz(fs_path)
|
3007
3049
|
else:
|
3008
3050
|
open_func = open
|
3009
|
-
|
3010
|
-
open_args = [fsenc(fs_path), "rb", 64 * 1024]
|
3051
|
+
open_args = [fsenc(fs_path), "rb", self.args.iobuf]
|
3011
3052
|
use_sendfile = (
|
3012
3053
|
# fmt: off
|
3013
3054
|
not self.tls
|
@@ -3132,7 +3173,7 @@ class HttpCli(object):
|
|
3132
3173
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
3133
3174
|
cfmt = ""
|
3134
3175
|
if self.thumbcli and not self.args.no_bacode:
|
3135
|
-
for zs in ("opus", "w", "j"):
|
3176
|
+
for zs in ("opus", "mp3", "w", "j"):
|
3136
3177
|
if zs in self.ouparam or uarg == zs:
|
3137
3178
|
cfmt = zs
|
3138
3179
|
|
@@ -3142,6 +3183,7 @@ class HttpCli(object):
|
|
3142
3183
|
|
3143
3184
|
bgen = packer(
|
3144
3185
|
self.log,
|
3186
|
+
self.args,
|
3145
3187
|
fgen,
|
3146
3188
|
utf8="utf" in uarg,
|
3147
3189
|
pre_crc="crc" in uarg,
|
@@ -3219,7 +3261,7 @@ class HttpCli(object):
|
|
3219
3261
|
sz_md = 0
|
3220
3262
|
lead = b""
|
3221
3263
|
fullfile = b""
|
3222
|
-
for buf in yieldfile(fs_path):
|
3264
|
+
for buf in yieldfile(fs_path, self.args.iobuf):
|
3223
3265
|
if sz_md < max_sz:
|
3224
3266
|
fullfile += buf
|
3225
3267
|
else:
|
@@ -3292,7 +3334,7 @@ class HttpCli(object):
|
|
3292
3334
|
if fullfile:
|
3293
3335
|
self.s.sendall(fullfile)
|
3294
3336
|
else:
|
3295
|
-
for buf in yieldfile(fs_path):
|
3337
|
+
for buf in yieldfile(fs_path, self.args.iobuf):
|
3296
3338
|
self.s.sendall(html_bescape(buf))
|
3297
3339
|
|
3298
3340
|
self.s.sendall(html[1])
|
@@ -3540,7 +3582,7 @@ class HttpCli(object):
|
|
3540
3582
|
|
3541
3583
|
dst = dst[len(top) + 1 :]
|
3542
3584
|
|
3543
|
-
ret = self.gen_tree(top, dst)
|
3585
|
+
ret = self.gen_tree(top, dst, self.uparam.get("k", ""))
|
3544
3586
|
if self.is_vproxied:
|
3545
3587
|
parents = self.args.R.split("/")
|
3546
3588
|
for parent in reversed(parents):
|
@@ -3550,18 +3592,25 @@ class HttpCli(object):
|
|
3550
3592
|
self.reply(zs.encode("utf-8"), mime="application/json")
|
3551
3593
|
return True
|
3552
3594
|
|
3553
|
-
def gen_tree(self, top , target ) :
|
3595
|
+
def gen_tree(self, top , target , dk ) :
|
3554
3596
|
ret = {}
|
3555
3597
|
excl = None
|
3556
3598
|
if target:
|
3557
3599
|
excl, target = (target.split("/", 1) + [""])[:2]
|
3558
|
-
sub = self.gen_tree("/".join([top, excl]).strip("/"), target)
|
3600
|
+
sub = self.gen_tree("/".join([top, excl]).strip("/"), target, dk)
|
3559
3601
|
ret["k" + quotep(excl)] = sub
|
3560
3602
|
|
3561
3603
|
vfs = self.asrv.vfs
|
3604
|
+
dk_sz = False
|
3605
|
+
if dk:
|
3606
|
+
vn, rem = vfs.get(top, self.uname, False, False)
|
3607
|
+
if vn.flags.get("dks") and self._use_dirkey(vn.canonical(rem)):
|
3608
|
+
dk_sz = vn.flags.get("dk")
|
3609
|
+
|
3562
3610
|
dots = False
|
3611
|
+
fsroot = ""
|
3563
3612
|
try:
|
3564
|
-
vn, rem = vfs.get(top, self.uname,
|
3613
|
+
vn, rem = vfs.get(top, self.uname, not dk_sz, False)
|
3565
3614
|
fsroot, vfs_ls, vfs_virt = vn.ls(
|
3566
3615
|
rem,
|
3567
3616
|
self.uname,
|
@@ -3569,7 +3618,9 @@ class HttpCli(object):
|
|
3569
3618
|
[[True, False], [False, True]],
|
3570
3619
|
)
|
3571
3620
|
dots = self.uname in vn.axs.udot
|
3621
|
+
dk_sz = vn.flags.get("dk")
|
3572
3622
|
except:
|
3623
|
+
dk_sz = None
|
3573
3624
|
vfs_ls = []
|
3574
3625
|
vfs_virt = {}
|
3575
3626
|
for v in self.rvol:
|
@@ -3584,6 +3635,14 @@ class HttpCli(object):
|
|
3584
3635
|
|
3585
3636
|
dirs = [quotep(x) for x in dirs if x != excl]
|
3586
3637
|
|
3638
|
+
if dk_sz and fsroot:
|
3639
|
+
kdirs = []
|
3640
|
+
for dn in dirs:
|
3641
|
+
ap = os.path.join(fsroot, dn)
|
3642
|
+
zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_sz]
|
3643
|
+
kdirs.append(dn + "?k=" + zs)
|
3644
|
+
dirs = kdirs
|
3645
|
+
|
3587
3646
|
for x in vfs_virt:
|
3588
3647
|
if x != excl:
|
3589
3648
|
try:
|
@@ -3848,6 +3907,7 @@ class HttpCli(object):
|
|
3848
3907
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
3849
3908
|
|
3850
3909
|
is_dir = stat.S_ISDIR(st.st_mode)
|
3910
|
+
is_dk = False
|
3851
3911
|
fk_pass = False
|
3852
3912
|
icur = None
|
3853
3913
|
if is_dir and (e2t or e2d):
|
@@ -3856,7 +3916,7 @@ class HttpCli(object):
|
|
3856
3916
|
icur = idx.get_cur(dbv.realpath)
|
3857
3917
|
|
3858
3918
|
th_fmt = self.uparam.get("th")
|
3859
|
-
if self.can_read:
|
3919
|
+
if self.can_read or (self.can_get and vn.flags.get("dk")):
|
3860
3920
|
if th_fmt is not None:
|
3861
3921
|
nothumb = "dthumb" in dbv.flags
|
3862
3922
|
if is_dir:
|
@@ -3962,8 +4022,11 @@ class HttpCli(object):
|
|
3962
4022
|
|
3963
4023
|
return self.tx_file(abspath)
|
3964
4024
|
|
3965
|
-
elif is_dir and not self.can_read
|
3966
|
-
|
4025
|
+
elif is_dir and not self.can_read:
|
4026
|
+
if self._use_dirkey(abspath):
|
4027
|
+
is_dk = True
|
4028
|
+
elif not self.can_write:
|
4029
|
+
return self.tx_404(True)
|
3967
4030
|
|
3968
4031
|
srv_info = []
|
3969
4032
|
|
@@ -3985,7 +4048,7 @@ class HttpCli(object):
|
|
3985
4048
|
srv_infot = "</span> // <span>".join(srv_info)
|
3986
4049
|
|
3987
4050
|
perms = []
|
3988
|
-
if self.can_read:
|
4051
|
+
if self.can_read or is_dk:
|
3989
4052
|
perms.append("read")
|
3990
4053
|
if self.can_write:
|
3991
4054
|
perms.append("write")
|
@@ -4113,7 +4176,7 @@ class HttpCli(object):
|
|
4113
4176
|
if not self.conn.hsrv.prism:
|
4114
4177
|
j2a["no_prism"] = True
|
4115
4178
|
|
4116
|
-
if not self.can_read:
|
4179
|
+
if not self.can_read and not is_dk:
|
4117
4180
|
if is_ls:
|
4118
4181
|
return self.tx_ls(ls_ret)
|
4119
4182
|
|
@@ -4166,8 +4229,15 @@ class HttpCli(object):
|
|
4166
4229
|
):
|
4167
4230
|
ls_names = exclude_dotfiles(ls_names)
|
4168
4231
|
|
4232
|
+
add_dk = vf.get("dk")
|
4169
4233
|
add_fk = vf.get("fk")
|
4170
4234
|
fk_alg = 2 if "fka" in vf else 1
|
4235
|
+
if add_dk:
|
4236
|
+
if vf.get("dky"):
|
4237
|
+
add_dk = False
|
4238
|
+
else:
|
4239
|
+
zs = self.gen_fk(2, self.args.dk_salt, abspath, 0, 0)[:add_dk]
|
4240
|
+
ls_ret["dk"] = cgv["dk"] = zs
|
4171
4241
|
|
4172
4242
|
dirs = []
|
4173
4243
|
files = []
|
@@ -4195,6 +4265,12 @@ class HttpCli(object):
|
|
4195
4265
|
href += "/"
|
4196
4266
|
if self.args.no_zip:
|
4197
4267
|
margin = "DIR"
|
4268
|
+
elif add_dk:
|
4269
|
+
zs = absreal(fspath)
|
4270
|
+
margin = '<a href="%s?k=%s&zip" rel="nofollow">zip</a>' % (
|
4271
|
+
quotep(href),
|
4272
|
+
self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
|
4273
|
+
)
|
4198
4274
|
else:
|
4199
4275
|
margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (quotep(href),)
|
4200
4276
|
elif fn in hist:
|
@@ -4235,6 +4311,11 @@ class HttpCli(object):
|
|
4235
4311
|
0 if ANYWIN else inf.st_ino,
|
4236
4312
|
)[:add_fk],
|
4237
4313
|
)
|
4314
|
+
elif add_dk and is_dir:
|
4315
|
+
href = "%s?k=%s" % (
|
4316
|
+
quotep(href),
|
4317
|
+
self.gen_fk(2, self.args.dk_salt, fspath, 0, 0)[:add_dk],
|
4318
|
+
)
|
4238
4319
|
else:
|
4239
4320
|
href = quotep(href)
|
4240
4321
|
|
@@ -4253,6 +4334,9 @@ class HttpCli(object):
|
|
4253
4334
|
files.append(item)
|
4254
4335
|
item["rd"] = rem
|
4255
4336
|
|
4337
|
+
if is_dk and not vf.get("dks"):
|
4338
|
+
dirs = []
|
4339
|
+
|
4256
4340
|
if (
|
4257
4341
|
self.cookies.get("idxh") == "y"
|
4258
4342
|
and "ls" not in self.uparam
|
copyparty/mtag.py
CHANGED
@@ -545,8 +545,7 @@ class MTag(object):
|
|
545
545
|
pypath = str(os.pathsep.join(zsl))
|
546
546
|
env["PYTHONPATH"] = pypath
|
547
547
|
except:
|
548
|
-
|
549
|
-
raise
|
548
|
+
raise # might be expected outside cpython
|
550
549
|
|
551
550
|
ret = {}
|
552
551
|
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
copyparty/star.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
+
import argparse
|
4
5
|
import re
|
5
6
|
import stat
|
6
7
|
import tarfile
|
@@ -38,11 +39,12 @@ class StreamTar(StreamArc):
|
|
38
39
|
def __init__(
|
39
40
|
self,
|
40
41
|
log ,
|
42
|
+
args ,
|
41
43
|
fgen ,
|
42
44
|
cmp = "",
|
43
45
|
**kwargs
|
44
46
|
):
|
45
|
-
super(StreamTar, self).__init__(log, fgen)
|
47
|
+
super(StreamTar, self).__init__(log, args, fgen)
|
46
48
|
|
47
49
|
self.ci = 0
|
48
50
|
self.co = 0
|
@@ -120,7 +122,7 @@ class StreamTar(StreamArc):
|
|
120
122
|
inf.gid = 0
|
121
123
|
|
122
124
|
self.ci += inf.size
|
123
|
-
with open(fsenc(src), "rb",
|
125
|
+
with open(fsenc(src), "rb", self.args.iobuf) as fo:
|
124
126
|
self.tar.addfile(inf, fo)
|
125
127
|
|
126
128
|
def _gen(self) :
|