Skip to content

Commit d866841

Browse files
committed
pkgres:
* pyz: yeet the resource tar which is now pointless thanks to pkgres * cache impresource stuff because pyz lookups are Extremely slow * prefer tx_file when possible for slightly better performance * use hardcoded list of expected resources instead of dynamic discovery at runtime; much simpler and probably safer * fix some forgotten resources (copying.txt, insecure.pem) * fix loading jinja templates on windows
1 parent a462a64 commit d866841

File tree

10 files changed

+185
-234
lines changed

10 files changed

+185
-234
lines changed

copyparty/__init__.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
TYPE_CHECKING = False
1717

1818
if True:
19-
from typing import Any, Callable
19+
from types import ModuleType
20+
21+
from typing import Any, Callable, Optional
2022

2123
PY2 = sys.version_info < (3,)
2224
PY36 = sys.version_info > (3, 6)
@@ -51,10 +53,63 @@
5153
except:
5254
CORES = (os.cpu_count() if hasattr(os, "cpu_count") else 0) or 2
5355

56+
# all embedded resources to be retrievable over http
57+
zs = """
58+
web/a/partyfuse.py
59+
web/a/u2c.py
60+
web/a/webdav-cfg.bat
61+
web/baguettebox.js
62+
web/browser.css
63+
web/browser.html
64+
web/browser.js
65+
web/browser2.html
66+
web/cf.html
67+
web/copyparty.gif
68+
web/dd/2.png
69+
web/dd/3.png
70+
web/dd/4.png
71+
web/dd/5.png
72+
web/deps/busy.mp3
73+
web/deps/easymde.css
74+
web/deps/easymde.js
75+
web/deps/marked.js
76+
web/deps/mini-fa.css
77+
web/deps/mini-fa.woff
78+
web/deps/prism.css
79+
web/deps/prism.js
80+
web/deps/prismd.css
81+
web/deps/scp.woff2
82+
web/deps/sha512.ac.js
83+
web/deps/sha512.hw.js
84+
web/md.css
85+
web/md.html
86+
web/md.js
87+
web/md2.css
88+
web/md2.js
89+
web/mde.css
90+
web/mde.html
91+
web/mde.js
92+
web/msg.css
93+
web/msg.html
94+
web/shares.css
95+
web/shares.html
96+
web/shares.js
97+
web/splash.css
98+
web/splash.html
99+
web/splash.js
100+
web/svcs.html
101+
web/svcs.js
102+
web/ui.css
103+
web/up2k.js
104+
web/util.js
105+
web/w.hash.js
106+
"""
107+
RES = set(zs.strip().split("\n"))
108+
54109

55110
class EnvParams(object):
56111
def __init__(self) -> None:
57-
self.pkg = None
112+
self.pkg: Optional[ModuleType] = None
58113
self.t0 = time.time()
59114
self.mod = ""
60115
self.cfg = ""

copyparty/__main__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
b64enc,
5959
dedent,
6060
has_resource,
61+
load_resource,
6162
min_ex,
6263
pybin,
6364
termsize,
@@ -217,6 +218,7 @@ def get_unixdir() -> str:
217218

218219
raise Exception("could not find a writable path for config")
219220

221+
assert __package__ # !rm
220222
E.pkg = sys.modules[__package__]
221223
E.mod = os.path.dirname(os.path.realpath(__file__))
222224
if E.mod.endswith("__init__"):
@@ -520,14 +522,18 @@ def sfx_tpoke(top: str):
520522

521523

522524
def showlic() -> None:
523-
p = os.path.join(E.mod, "res", "COPYING.txt")
524-
if not os.path.exists(p):
525+
try:
526+
with load_resource(E, "res/COPYING.txt") as f:
527+
buf = f.read()
528+
except:
529+
buf = b""
530+
531+
if buf:
532+
print(buf.decode("utf-8", "replace"))
533+
else:
525534
print("no relevant license info to display")
526535
return
527536

528-
with open(p, "rb") as f:
529-
print(f.read().decode("utf-8", "replace"))
530-
531537

532538
def get_sects():
533539
return [
@@ -1567,16 +1573,13 @@ def run_argparse(
15671573
return ret
15681574

15691575

1570-
def main(argv: Optional[list[str]] = None, rsrc: Optional[str] = None) -> None:
1576+
def main(argv: Optional[list[str]] = None) -> None:
15711577
time.strptime("19970815", "%Y%m%d") # python#7980
15721578
if WINDOWS:
15731579
os.system("rem") # enables colors
15741580

15751581
init_E(E)
15761582

1577-
if rsrc: # pyz
1578-
E.mod = rsrc
1579-
15801583
if argv is None:
15811584
argv = sys.argv
15821585

copyparty/cert.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import time
88

99
from .__init__ import ANYWIN
10-
from .util import Netdev, runcmd, wrename, wunlink
10+
from .util import Netdev, load_resource, runcmd, wrename, wunlink
1111

1212
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
1313

@@ -29,13 +29,15 @@ def ensure_cert(log: "RootLogger", args) -> None:
2929
3030
i feel awful about this and so should they
3131
"""
32-
cert_insec = os.path.join(args.E.mod, "res/insecure.pem")
32+
with load_resource(args.E, "res/insecure.pem") as f:
33+
cert_insec = f.read()
3334
cert_appdata = os.path.join(args.E.cfg, "cert.pem")
3435
if not os.path.isfile(args.cert):
3536
if cert_appdata != args.cert:
3637
raise Exception("certificate file does not exist: " + args.cert)
3738

38-
shutil.copy(cert_insec, args.cert)
39+
with open(args.cert, "wb") as f:
40+
f.write(cert_insec)
3941

4042
with open(args.cert, "rb") as f:
4143
buf = f.read()
@@ -50,7 +52,9 @@ def ensure_cert(log: "RootLogger", args) -> None:
5052
raise Exception(m + "private key must appear before server certificate")
5153

5254
try:
53-
if filecmp.cmp(args.cert, cert_insec):
55+
with open(args.cert, "rb") as f:
56+
active_cert = f.read()
57+
if active_cert == cert_insec:
5458
t = "using default TLS certificate; https will be insecure:\033[36m {}"
5559
log("cert", t.format(args.cert), 3)
5660
except:
@@ -151,14 +155,22 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
151155
raise Exception("no useable cert found")
152156

153157
expired = time.time() + args.crt_sdays * 60 * 60 * 24 * 0.5 > expiry
154-
cert_insec = os.path.join(args.E.mod, "res/insecure.pem")
158+
if expired:
159+
raise Exception("old server-cert has expired")
160+
155161
for n in names:
156162
if n not in inf["sans"]:
157163
raise Exception("does not have {}".format(n))
158-
if expired:
159-
raise Exception("old server-cert has expired")
160-
if not filecmp.cmp(args.cert, cert_insec):
164+
165+
with load_resource(args.E, "res/insecure.pem") as f:
166+
cert_insec = f.read()
167+
168+
with open(args.cert, "rb") as f:
169+
active_cert = f.read()
170+
171+
if active_cert and active_cert != cert_insec:
161172
return
173+
162174
except Exception as ex:
163175
log("cert", "will create new server-cert; {}".format(ex))
164176

copyparty/httpcli.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
except:
3333
pass
3434

35-
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, EnvParams, unicode
35+
from .__init__ import ANYWIN, PY2, RES, TYPE_CHECKING, EnvParams, unicode
3636
from .__version__ import S_VERSION
3737
from .authsrv import VFS # typechk
3838
from .bos import bos
@@ -67,8 +67,8 @@
6767
get_df,
6868
get_spd,
6969
guess_mime,
70-
gzip_orig_sz,
7170
gzip_file_orig_sz,
71+
gzip_orig_sz,
7272
has_resource,
7373
hashcopy,
7474
hidedir,
@@ -1097,13 +1097,17 @@ def handle_get(self) -> bool:
10971097
if self.vpath == ".cpr/metrics":
10981098
return self.conn.hsrv.metrics.tx(self)
10991099

1100-
static_path = os.path.join("web", self.vpath[5:])
1101-
if static_path in self.conn.hsrv.statics:
1102-
return self.tx_res(static_path)
1100+
res_path = "web/" + self.vpath[5:]
1101+
if res_path in RES:
1102+
ap = os.path.join(self.E.mod, res_path)
1103+
if bos.path.exists(ap) or bos.path.exists(ap + ".gz"):
1104+
return self.tx_file(ap)
1105+
else:
1106+
return self.tx_res(res_path)
11031107

1104-
if not undot(static_path).startswith("web"):
1108+
if res_path != undot(res_path):
11051109
t = "malicious user; attempted path traversal [{}] => [{}]"
1106-
self.log(t.format(self.vpath, static_path), 1)
1110+
self.log(t.format(self.vpath, res_path), 1)
11071111
self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
11081112

11091113
self.tx_404()
@@ -3415,6 +3419,7 @@ def tx_res(self, req_path: str) -> bool:
34153419
self.args.s_wr_slp,
34163420
not self.args.no_poll,
34173421
)
3422+
res.close()
34183423

34193424
if remains > 0:
34203425
logmsg += " \033[31m" + unicode(file_sz - remains) + "\033[0m"

copyparty/httpsrv.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
Magician,
6767
Netdev,
6868
NetMap,
69-
absreal,
7069
build_netmap,
7170
has_resource,
7271
ipnorm,
@@ -76,9 +75,7 @@
7675
spack,
7776
start_log_thrs,
7877
start_stackmon,
79-
stat_resource,
8078
ub64enc,
81-
walk_resources,
8279
)
8380

8481
if TYPE_CHECKING:
@@ -96,7 +93,7 @@
9693

9794

9895
def load_jinja2_resource(E: EnvParams, name: str):
99-
return load_resource(E, os.path.join("web", name), "r").read()
96+
return load_resource(E, "web/" + name, "r").read()
10097

10198

10299
class HttpSrv(object):
@@ -174,15 +171,12 @@ def __init__(self, broker: "BrokerCli", nid: Optional[int]) -> None:
174171
"cf",
175172
]
176173
self.j2 = {x: env.get_template(x + ".html") for x in jn}
177-
self.prism = has_resource(self.E, os.path.join("web", "deps", "prism.js.gz"))
174+
self.prism = has_resource(self.E, "web/deps/prism.js.gz")
178175

179176
self.ipa_nm = build_netmap(self.args.ipa)
180177
self.xff_nm = build_netmap(self.args.xff_src)
181178
self.xff_lan = build_netmap("lan")
182179

183-
self.statics: set[str] = set()
184-
self._build_statics()
185-
186180
self.ptn_cc = re.compile(r"[\x00-\x1f]")
187181
self.ptn_hsafe = re.compile(r"[\x00-\x1f<>\"'&]")
188182

@@ -216,14 +210,6 @@ def post_init(self) -> None:
216210
except:
217211
pass
218212

219-
def _build_statics(self) -> None:
220-
for dp, _, df in walk_resources(self.E, "web"):
221-
for fn in df:
222-
ap = os.path.join(dp, fn)
223-
self.statics.add(ap)
224-
if ap.endswith(".gz"):
225-
self.statics.add(ap[:-3])
226-
227213
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
228214
ips = set()
229215
for ip, _ in self.bound:
@@ -543,20 +529,10 @@ def cachebuster(self) -> str:
543529

544530
v = self.E.t0
545531
try:
546-
for (base, dirs, files) in walk_resources(self.E, "web"):
547-
inf = stat_resource(self.E, base)
548-
if inf:
532+
with os.scandir(os.path.join(self.E.mod, "web")) as dh:
533+
for fh in dh:
534+
inf = fh.stat()
549535
v = max(v, inf.st_mtime)
550-
for d in dirs:
551-
inf = stat_resource(self.E, os.path.join(base, d))
552-
if inf:
553-
v = max(v, inf.st_mtime)
554-
for f in files:
555-
inf = stat_resource(self.E, os.path.join(base, e))
556-
if inf:
557-
v = max(v, inf.st_mtime)
558-
# only do top-level
559-
break
560536
except:
561537
pass
562538

0 commit comments

Comments
 (0)