diff --git a/copyparty/__main__.py b/copyparty/__main__.py index d39dcae3..17dd82c1 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -619,9 +619,9 @@ def get_sects(): \033[32macid\033[0m = extremely safe but slow; the old default. Should never lose any data no matter what - \033[32mswal\033[0m = 2.4x faster uploads yet 99.9%% as safe -- theoretical chance of losing metadata for the ~200 most recently uploaded files if there's a power-loss or your OS crashes + \033[32mswal\033[0m = 2.4x faster uploads yet 99.9% as safe -- theoretical chance of losing metadata for the ~200 most recently uploaded files if there's a power-loss or your OS crashes - \033[32mwal\033[0m = another 21x faster on HDDs yet 90%% as safe; same pitfall as \033[33mswal\033[0m except more likely + \033[32mwal\033[0m = another 21x faster on HDDs yet 90% as safe; same pitfall as \033[33mswal\033[0m except more likely \033[32myolo\033[0m = another 1.5x faster, and removes the occasional sudden upload-pause while the disk syncs, but now you're at risk of losing the entire database in a powerloss / OS-crash @@ -710,6 +710,8 @@ def add_network(ap): ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") else: 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)") + ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)") + 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") ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes") ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds") ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 9015de72..1d887103 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -219,7 +219,7 @@ class HttpCli(object): try: self.s.settimeout(2) - headerlines = read_header(self.sr) + headerlines = read_header(self.sr, self.args.s_thead, self.args.s_thead) self.in_hdr_recv = False if not headerlines: return False @@ -416,6 +416,8 @@ class HttpCli(object): self.can_upget, ) = self.asrv.vfs.can_access(self.vpath, self.uname) + self.s.settimeout(self.args.s_tbody or None) + try: cors_k = self._cors() if self.mode in ("GET", "HEAD"): @@ -558,7 +560,6 @@ class HttpCli(object): try: # best practice to separate headers and body into different packets - self.s.settimeout(None) self.s.sendall("\r\n".join(response).encode("utf-8") + b"\r\n\r\n") except: raise Pebkac(400, "client d/c while replying headers") @@ -1206,7 +1207,6 @@ class HttpCli(object): if self.headers.get("expect", "").lower() == "100-continue": try: - self.s.settimeout(None) self.s.sendall(b"HTTP/1.1 100 Continue\r\n\r\n") except: raise Pebkac(400, "client d/c before 100 continue") @@ -1218,7 +1218,6 @@ class HttpCli(object): if self.headers.get("expect", "").lower() == "100-continue": try: - self.s.settimeout(None) self.s.sendall(b"HTTP/1.1 100 Continue\r\n\r\n") except: raise Pebkac(400, "client d/c before 100 continue") diff --git a/copyparty/util.py b/copyparty/util.py index b618021f..fe24c035 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -537,7 +537,7 @@ class _Unrecv(object): self.log = log self.buf: bytes = b"" - def recv(self, nbytes: int) -> bytes: + def recv(self, nbytes: int, spins: int = 1) -> bytes: if self.buf: ret = self.buf[:nbytes] self.buf = self.buf[nbytes:] @@ -548,6 +548,10 @@ class _Unrecv(object): ret = self.s.recv(nbytes) break except socket.timeout: + spins -= 1 + if spins <= 0: + ret = b"" + break continue except: ret = b"" @@ -590,7 +594,7 @@ class _LUnrecv(object): self.log = log self.buf = b"" - def recv(self, nbytes: int) -> bytes: + def recv(self, nbytes: int, spins: int) -> bytes: if self.buf: ret = self.buf[:nbytes] self.buf = self.buf[nbytes:] @@ -1292,7 +1296,7 @@ class MultipartParser(object): rfc1341/rfc1521/rfc2047/rfc2231/rfc2388/rfc6266/the-real-world (only the fallback non-js uploader relies on these filenames) """ - for ln in read_header(self.sr): + for ln in read_header(self.sr, 2, 2592000): self.log(ln) m = self.re_ctype.match(ln) @@ -1492,15 +1496,15 @@ def get_boundary(headers: dict[str, str]) -> str: return m.group(2) -def read_header(sr: Unrecv) -> list[str]: +def read_header(sr: Unrecv, t_idle: int, t_tot: int) -> list[str]: t0 = time.time() ret = b"" while True: - if time.time() - t0 > 120: + if time.time() - t0 >= t_tot: return [] try: - ret += sr.recv(1024) + ret += sr.recv(1024, t_idle // 2) except: if not ret: return [] diff --git a/tests/util.py b/tests/util.py index 6a20ad53..5dedb37d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -107,6 +107,9 @@ class Cfg(Namespace): ex = "css_browser hist js_browser no_forget no_hash no_idx" ka.update(**{k: None for k in ex.split()}) + ex = "s_thead s_tbody" + ka.update(**{k: 9 for k in ex.split()}) + ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo" ka.update(**{k: 0 for k in ex.split()})