diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py
index 59dc2ef0..1497429f 100644
--- a/copyparty/authsrv.py
+++ b/copyparty/authsrv.py
@@ -269,7 +269,7 @@ class AuthSrv(object):
if self.warn_anonwrite:
self.warn_anonwrite = False
self.log(
- "\033[31manyone can write to the current directory: {}\033[0m".format(
+ "\033[31manyone can read/write the current directory: {}\033[0m".format(
os.getcwd()
)
)
diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py
index be2d0a11..1f1af0f6 100644
--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -31,7 +31,6 @@ class HttpCli(object):
self.log_func = conn.log_func
self.log_src = conn.log_src
- self.ok = True
self.bufsz = 1024 * 32
self.absolute_urls = False
self.out_headers = {}
@@ -42,13 +41,17 @@ class HttpCli(object):
def run(self):
try:
headerlines = read_header(self.sr)
- except:
- return False
+ if not headerlines:
+ return False
- try:
- mode, self.req, _ = headerlines[0].split(" ")
- except:
- raise Pebkac("bad headers:\n" + "\n".join(headerlines))
+ try:
+ mode, self.req, _ = headerlines[0].split(" ")
+ except:
+ raise Pebkac("bad headers:\n" + "\n".join(headerlines))
+
+ except Pebkac as ex:
+ self.loud_reply(str(ex))
+ return False
self.headers = {}
for header_line in headerlines[1:]:
@@ -97,17 +100,19 @@ class HttpCli(object):
try:
if mode == "GET":
- self.handle_get()
+ return self.handle_get()
elif mode == "POST":
- self.handle_post()
+ return self.handle_post()
else:
- self.loud_reply('invalid HTTP mode "{0}"'.format(mode))
+ raise Pebkac('invalid HTTP mode "{0}"'.format(mode))
except Pebkac as ex:
- self.loud_reply(str(ex))
- return False
+ try:
+ self.loud_reply(str(ex))
+ except Pebkac:
+ pass
- return self.ok
+ return False
def reply(self, body, status="200 OK", mime="text/html", headers=[]):
# TODO something to reply with user-supplied values safely
@@ -122,8 +127,10 @@ class HttpCli(object):
response.extend(headers)
response_str = "\r\n".join(response).encode("utf-8")
- if self.ok:
+ try:
self.s.send(response_str + b"\r\n\r\n" + body)
+ except:
+ raise Pebkac("client disconnected before http response")
return body
@@ -218,40 +225,53 @@ class HttpCli(object):
raise Exception("that was close")
files = []
+ errmsg = ""
t0 = time.time()
- for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen):
- if not p_file:
- self.log("discarding incoming file without filename")
+ try:
+ for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen):
+ if not p_file:
+ self.log("discarding incoming file without filename")
+ # fallthrough
- fn = os.devnull
- if p_file and not nullwrite:
- fn = os.path.join(vfs.realpath, rem, sanitize_fn(p_file))
+ fn = os.devnull
+ if p_file and not nullwrite:
+ fdir = os.path.join(vfs.realpath, rem)
+ fn = os.path.join(fdir, sanitize_fn(p_file))
- # TODO broker which avoid this race
- # and provides a new filename if taken
- if os.path.exists(fsenc(fn)):
- fn += ".{:.6f}".format(time.time())
+ if not os.path.isdir(fsenc(fdir)):
+ raise Pebkac("that folder does not exist")
- try:
- with open(fn, "wb") as f:
- self.log("writing to {0}".format(fn))
- sz, sha512 = hashcopy(self.conn, p_data, f)
- if sz == 0:
- break
+ # TODO broker which avoid this race
+ # and provides a new filename if taken
+ if os.path.exists(fsenc(fn)):
+ fn += ".{:.6f}".format(time.time())
- files.append([sz, sha512])
+ try:
+ with open(fsenc(fn), "wb") as f:
+ self.log("writing to {0}".format(fn))
+ sz, sha512 = hashcopy(self.conn, p_data, f)
+ if sz == 0:
+ raise Pebkac("empty files in post")
- except FileNotFoundError:
- raise Pebkac("create that folder before uploading to it")
+ files.append([sz, sha512])
- self.parser.drop()
+ except Pebkac:
+ if not nullwrite:
+ os.rename(fsenc(fn), fsenc(fn + ".PARTIAL"))
+
+ raise
+
+ except Pebkac as ex:
+ errmsg = str(ex)
td = time.time() - t0
sz_total = sum(x[0] for x in files)
spd = (sz_total / td) / (1024 * 1024)
status = "OK"
- if not self.ok:
+ if errmsg:
+ self.log(errmsg)
+ errmsg = "ERROR: " + errmsg
status = "ERROR"
msg = "{0} // {1} bytes // {2:.3f} MiB/s\n".format(status, sz_total, spd)
@@ -261,15 +281,7 @@ class HttpCli(object):
# truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64
- html = self.conn.tpl_msg.render(
- h2='return to /{}'.format(
- quotep(self.vpath), cgi.escape(self.vpath, quote=True)
- ),
- pre=msg,
- )
self.log(msg)
- self.reply(html.encode("utf-8"))
-
if not nullwrite:
# TODO this is bad
log_fn = "up.{:.6f}.txt".format(t0)
@@ -284,9 +296,21 @@ class HttpCli(object):
]
)
+ "\n"
+ + errmsg
+ + "\n"
).encode("utf-8")
)
+ html = self.conn.tpl_msg.render(
+ h2='return to /{}'.format(
+ quotep(self.vpath), cgi.escape(self.vpath, quote=True)
+ ),
+ pre=msg,
+ )
+ self.reply(html.encode("utf-8"))
+
+ self.parser.drop()
+
def tx_file(self, path):
sz = os.path.getsize(fsenc(path))
mime = mimetypes.guess_type(path)[0]
@@ -296,11 +320,10 @@ class HttpCli(object):
"utf-8"
)
- if self.ok:
- self.s.send(header)
+ self.s.send(header)
with open(fsenc(path), "rb") as f:
- while self.ok:
+ while True:
buf = f.read(4096)
if not buf:
break
@@ -309,7 +332,6 @@ class HttpCli(object):
self.s.send(buf)
except ConnectionResetError:
return False
- # TODO propagate (self.ok or return)
def tx_mounts(self):
html = self.conn.tpl_mounts.render(this=self)
diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py
index 27744c72..bc56e8d9 100644
--- a/copyparty/httpconn.py
+++ b/copyparty/httpconn.py
@@ -24,7 +24,6 @@ class HttpConn(object):
self.sr = Unrecv(sck)
self.workload = 0
- self.ok = True
self.log_func = log_func
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26)
diff --git a/copyparty/util.py b/copyparty/util.py
index df7fb9cc..c6d49385 100644
--- a/copyparty/util.py
+++ b/copyparty/util.py
@@ -159,7 +159,7 @@ class MultipartParser(object):
buf = self.sr.recv(bufsz)
if not buf:
# abort: client disconnected
- raise Exception("client disconnected during post")
+ raise Pebkac("client disconnected during post")
while True:
ofs = buf.find(self.boundary)
@@ -193,7 +193,7 @@ class MultipartParser(object):
buf2 = self.sr.recv(bufsz)
if not buf2:
# abort: client disconnected
- raise Exception("client disconnected during post")
+ raise Pebkac("client disconnected during post")
buf += buf2
@@ -288,7 +288,13 @@ def read_header(sr):
buf = sr.recv(n)
if not buf:
- raise Exception("failed to read headers")
+ if not ret:
+ return None
+
+ raise Pebkac(
+ "protocol error while reading headers:\n"
+ + ret.decode("utf-8", "replace")
+ )
ret += buf