Files
copyparty/tests/util.py

267 lines
7.1 KiB
Python
Raw Normal View History

2023-08-20 17:58:06 +00:00
#!/usr/bin/env python3
# coding: utf-8
from __future__ import print_function, unicode_literals
import os
2023-11-30 17:33:07 +00:00
import platform
2023-07-23 15:43:38 +00:00
import re
2021-06-01 05:49:41 +02:00
import shutil
import subprocess as sp
2023-11-30 17:33:07 +00:00
import sys
import tempfile
import threading
import time
2022-07-27 00:15:49 +02:00
from argparse import Namespace
2023-11-30 17:33:07 +00:00
import jinja2
2021-06-04 19:35:08 +02:00
WINDOWS = platform.system() == "Windows"
ANYWIN = WINDOWS or sys.platform in ["msys"]
MACOS = platform.system() == "Darwin"
2023-11-30 17:33:07 +00:00
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore
2023-09-11 01:46:25 +00:00
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
2021-06-04 19:35:08 +02:00
def nah(*a, **ka):
return False
2023-08-20 17:58:06 +00:00
def eprint(*a, **ka):
ka["file"] = sys.stderr
print(*a, **ka)
sys.stderr.flush()
2021-06-04 19:35:08 +02:00
if MACOS:
import posixpath
posixpath.islink = nah
os.path.islink = nah
# 25% faster; until any tests do symlink stuff
2022-09-18 00:16:40 +02:00
from copyparty.__init__ import E
from copyparty.__main__ import init_E
from copyparty.u2idx import U2idx
from copyparty.util import FHC, CachedDict, Garda, Unrecv
2021-06-04 19:35:08 +02:00
2022-09-18 00:16:40 +02:00
init_E(E)
2021-06-04 19:35:08 +02:00
2021-07-28 01:18:38 +02:00
def runcmd(argv):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
return [p.returncode, stdout, stderr]
2021-07-28 01:18:38 +02:00
def chkcmd(argv):
ok, sout, serr = runcmd(argv)
if ok != 0:
raise Exception(serr)
return sout, serr
def get_ramdisk():
2021-06-01 05:49:41 +02:00
def subdir(top):
ret = os.path.join(top, "cptd-{}".format(os.getpid()))
shutil.rmtree(ret, True)
os.mkdir(ret)
return ret
for vol in ["/dev/shm", "/Volumes/cptd"]: # nosec (singleton test)
if os.path.exists(vol):
2021-06-01 05:49:41 +02:00
return subdir(vol)
if os.path.exists("/Volumes"):
2021-06-01 05:49:41 +02:00
# hdiutil eject /Volumes/cptd/
2021-07-28 01:18:38 +02:00
devname, _ = chkcmd("hdiutil attach -nomount ram://131072".split())
devname = devname.strip()
print("devname: [{}]".format(devname))
for _ in range(10):
try:
2021-07-28 01:18:38 +02:00
_, _ = chkcmd(["diskutil", "eraseVolume", "HFS+", "cptd", devname])
2023-11-30 17:33:07 +00:00
with open("/Volumes/cptd/.metadata_never_index", "wb") as f:
f.write(b"orz")
2021-07-17 16:43:22 +02:00
2021-07-17 16:45:25 +02:00
try:
shutil.rmtree("/Volumes/cptd/.fseventsd")
except:
pass
2021-06-01 05:49:41 +02:00
return subdir("/Volumes/cptd")
except Exception as ex:
print(repr(ex))
time.sleep(0.25)
raise Exception("ramdisk creation failed")
ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
2023-11-30 17:33:07 +00:00
if not os.path.isdir(ret):
os.mkdir(ret)
2023-11-30 17:33:07 +00:00
return subdir(ret)
2022-07-27 00:15:49 +02:00
class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0):
2022-07-27 00:15:49 +02:00
ka = {}
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver xdev xlink xvol"
2022-07-27 00:15:49 +02:00
ka.update(**{k: False for k in ex.split()})
2024-04-25 22:25:38 +00:00
ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
2022-07-27 00:15:49 +02:00
ka.update(**{k: True for k in ex.split()})
ex = "ah_cli ah_gen css_browser hist js_browser mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
2022-07-27 00:15:49 +02:00
ka.update(**{k: None for k in ex.split()})
ex = "hash_mt srch_time u2abort u2j"
ka.update(**{k: 1 for k in ex.split()})
ex = "au_vol mtab_age reg_cap s_thead s_tbody th_convt"
2023-04-24 20:04:22 +00:00
ka.update(**{k: 9 for k in ex.split()})
ex = "db_act df k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
2022-07-27 00:15:49 +02:00
ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg bname doctitle exit favico idp_h_usr html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i tcolor textfiles unlist vname R RS SR"
2022-07-27 00:15:49 +02:00
ka.update(**{k: "" for k in ex.split()})
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
2023-01-28 13:35:49 +00:00
ka.update(**{k: [] for k in ex.split()})
ex = "exp_lg exp_md th_coversd"
ka.update(**{k: {} for k in ex.split()})
ka.update(ka0)
2022-07-27 00:15:49 +02:00
super(Cfg, self).__init__(
a=a or [],
v=v or [],
c=c,
2022-09-18 00:16:40 +02:00
E=E,
dbd="wal",
2023-12-17 22:30:22 +00:00
dk_salt="b" * 16,
fk_salt="a" * 16,
idp_gsep=re.compile("[|:;+,]"),
iobuf=256 * 1024,
lang="eng",
log_badpwd=1,
logout=573,
mte={"a": True},
mth={},
mtp=[],
mv_retry="0/0",
rm_retry="0/0",
s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024,
sort="href",
srch_hits=99999,
th_crop="y",
th_size="320x256",
th_x3="n",
2022-07-27 00:15:49 +02:00
u2sort="s",
u2ts="c",
unpost=600,
warksalt="hunter2",
2022-07-27 00:15:49 +02:00
**ka
)
2021-04-24 03:35:58 +02:00
class NullBroker(object):
2023-11-30 17:33:07 +00:00
def say(self, *args):
2022-06-16 01:07:15 +02:00
pass
2023-11-30 17:33:07 +00:00
def ask(self, *args):
2021-04-24 03:35:58 +02:00
pass
class VSock(object):
def __init__(self, buf):
self._query = buf
self._reply = b""
self.sendall = self.send
def recv(self, sz):
ret = self._query[:sz]
self._query = self._query[sz:]
return ret
def send(self, buf):
self._reply += buf
return len(buf)
2022-01-18 22:28:33 +01:00
def getsockname(self):
return ("a", 1)
2022-10-31 22:42:47 +00:00
def settimeout(self, a):
pass
class VHttpSrv(object):
def __init__(self, args, asrv, log):
self.args = args
self.asrv = asrv
self.log = log
2021-04-24 03:35:58 +02:00
self.broker = NullBroker()
2021-11-06 23:27:21 +01:00
self.prism = None
2022-09-23 22:53:51 +02:00
self.bans = {}
2023-11-11 17:38:43 +00:00
self.nreq = 0
self.nsus = 0
2021-04-24 03:35:58 +02:00
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
self.j2 = {x: J2_FILES for x in aliases}
2023-07-16 13:09:31 +00:00
self.gpwd = Garda("")
self.g404 = Garda("")
self.g403 = Garda("")
self.gurl = Garda("")
2023-07-16 13:09:31 +00:00
self.u2idx = None
2023-07-23 15:43:38 +00:00
self.ptn_cc = re.compile(r"[\x00-\x1f]")
def cachebuster(self):
return "a"
def get_u2idx(self):
self.u2idx = self.u2idx or U2idx(self)
return self.u2idx
class VHttpConn(object):
2021-06-11 23:01:13 +02:00
def __init__(self, args, asrv, log, buf):
self.t0 = time.time()
self.s = VSock(buf)
2023-11-30 17:33:07 +00:00
self.sr = Unrecv(self.s, None) # type: ignore
self.aclose = {}
self.addr = ("127.0.0.1", "42069")
self.args = args
2021-06-11 23:01:13 +02:00
self.asrv = asrv
self.bans = {}
self.freshen_pwd = 0.0
self.hsrv = VHttpSrv(args, asrv, log)
self.ico = None
self.ipa_nm = None
self.lf_url = None
self.log_func = log
self.log_src = "a"
2021-10-03 19:59:47 +02:00
self.mutex = threading.Lock()
self.pipes = CachedDict(1)
up2k: fix a mostly-harmless race as each chunk is written to the file, httpcli calls up2k.confirm_chunk to register the chunk as completed, and the reply indicates whether that was the final outstanding chunk, in which case httpcli closes the file descriptors since there's nothing more to write the issue is that the final chunk is registered as completed before the file descriptors are closed, meaning there could be writes that haven't finished flushing to disk yet if the client decides to issue another handshake during this window, up2k sees that all chunks are complete and calls up2k.finish_upload even as some threads might still be flushing the final writes to disk so the conditions to hit this bug were as follows (all must be true): * multiprocessing is disabled * there is a reverse-proxy * a client has several idle connections and reuses one of those * the server's filesystem is EXTREMELY slow, to the point where closing a file takes over 30 seconds the fix is to stop handshakes from being processed while a file is being closed, which is unfortunately a small bottleneck in that it prohibits initiating another upload while one is being finalized, but the required complexity to handle this better is probably not worth it (a separate mutex for each upload session or something like that) this issue is mostly harmless, partially because it is super tricky to hit (only aware of it happening synthetically), and because there is usually no harmful consequences; the worst-case is if this were to happen exactly as the server OS decides to crash, which would make the file appear to be fully uploaded even though it's missing some data (all extremely unlikely, but not impossible) there is no performance impact; if anything it should now accept new tcp connections slightly faster thanks to more granular locking
2024-02-13 19:24:06 +00:00
self.u2mutex = threading.Lock()
self.nbyte = 0
self.nid = None
self.nreq = -1
self.thumbcli = None
self.u2fh = FHC()
2024-03-21 18:51:23 +00:00
self.get_u2idx = self.hsrv.get_u2idx
2024-04-25 22:25:38 +00:00
if WINDOWS:
os.system("rem")