less todo (handle client/network errors)

This commit is contained in:
ed 2019-06-18 19:27:17 +00:00
parent bf3163be0f
commit 5b1e73ff71
4 changed files with 79 additions and 52 deletions

View file

@ -269,7 +269,7 @@ class AuthSrv(object):
if self.warn_anonwrite: if self.warn_anonwrite:
self.warn_anonwrite = False self.warn_anonwrite = False
self.log( 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() os.getcwd()
) )
) )

View file

@ -31,7 +31,6 @@ class HttpCli(object):
self.log_func = conn.log_func self.log_func = conn.log_func
self.log_src = conn.log_src self.log_src = conn.log_src
self.ok = True
self.bufsz = 1024 * 32 self.bufsz = 1024 * 32
self.absolute_urls = False self.absolute_urls = False
self.out_headers = {} self.out_headers = {}
@ -42,7 +41,7 @@ class HttpCli(object):
def run(self): def run(self):
try: try:
headerlines = read_header(self.sr) headerlines = read_header(self.sr)
except: if not headerlines:
return False return False
try: try:
@ -50,6 +49,10 @@ class HttpCli(object):
except: except:
raise Pebkac("bad headers:\n" + "\n".join(headerlines)) raise Pebkac("bad headers:\n" + "\n".join(headerlines))
except Pebkac as ex:
self.loud_reply(str(ex))
return False
self.headers = {} self.headers = {}
for header_line in headerlines[1:]: for header_line in headerlines[1:]:
k, v = header_line.split(":", 1) k, v = header_line.split(":", 1)
@ -97,17 +100,19 @@ class HttpCli(object):
try: try:
if mode == "GET": if mode == "GET":
self.handle_get() return self.handle_get()
elif mode == "POST": elif mode == "POST":
self.handle_post() return self.handle_post()
else: else:
self.loud_reply('invalid HTTP mode "{0}"'.format(mode)) raise Pebkac('invalid HTTP mode "{0}"'.format(mode))
except Pebkac as ex: except Pebkac as ex:
try:
self.loud_reply(str(ex)) self.loud_reply(str(ex))
return False except Pebkac:
pass
return self.ok return False
def reply(self, body, status="200 OK", mime="text/html", headers=[]): def reply(self, body, status="200 OK", mime="text/html", headers=[]):
# TODO something to reply with user-supplied values safely # TODO something to reply with user-supplied values safely
@ -122,8 +127,10 @@ class HttpCli(object):
response.extend(headers) response.extend(headers)
response_str = "\r\n".join(response).encode("utf-8") response_str = "\r\n".join(response).encode("utf-8")
if self.ok: try:
self.s.send(response_str + b"\r\n\r\n" + body) self.s.send(response_str + b"\r\n\r\n" + body)
except:
raise Pebkac("client disconnected before http response")
return body return body
@ -218,14 +225,21 @@ class HttpCli(object):
raise Exception("that was close") raise Exception("that was close")
files = [] files = []
errmsg = ""
t0 = time.time() t0 = time.time()
try:
for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen): for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen):
if not p_file: if not p_file:
self.log("discarding incoming file without filename") self.log("discarding incoming file without filename")
# fallthrough
fn = os.devnull fn = os.devnull
if p_file and not nullwrite: if p_file and not nullwrite:
fn = os.path.join(vfs.realpath, rem, sanitize_fn(p_file)) fdir = os.path.join(vfs.realpath, rem)
fn = os.path.join(fdir, sanitize_fn(p_file))
if not os.path.isdir(fsenc(fdir)):
raise Pebkac("that folder does not exist")
# TODO broker which avoid this race # TODO broker which avoid this race
# and provides a new filename if taken # and provides a new filename if taken
@ -233,25 +247,31 @@ class HttpCli(object):
fn += ".{:.6f}".format(time.time()) fn += ".{:.6f}".format(time.time())
try: try:
with open(fn, "wb") as f: with open(fsenc(fn), "wb") as f:
self.log("writing to {0}".format(fn)) self.log("writing to {0}".format(fn))
sz, sha512 = hashcopy(self.conn, p_data, f) sz, sha512 = hashcopy(self.conn, p_data, f)
if sz == 0: if sz == 0:
break raise Pebkac("empty files in post")
files.append([sz, sha512]) files.append([sz, sha512])
except FileNotFoundError: except Pebkac:
raise Pebkac("create that folder before uploading to it") if not nullwrite:
os.rename(fsenc(fn), fsenc(fn + ".PARTIAL"))
self.parser.drop() raise
except Pebkac as ex:
errmsg = str(ex)
td = time.time() - t0 td = time.time() - t0
sz_total = sum(x[0] for x in files) sz_total = sum(x[0] for x in files)
spd = (sz_total / td) / (1024 * 1024) spd = (sz_total / td) / (1024 * 1024)
status = "OK" status = "OK"
if not self.ok: if errmsg:
self.log(errmsg)
errmsg = "ERROR: " + errmsg
status = "ERROR" status = "ERROR"
msg = "{0} // {1} bytes // {2:.3f} MiB/s\n".format(status, sz_total, spd) 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; # truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64 # using SHA-512/224, optionally SHA-512/256 = :64
html = self.conn.tpl_msg.render(
h2='<a href="/{}">return to /{}</a>'.format(
quotep(self.vpath), cgi.escape(self.vpath, quote=True)
),
pre=msg,
)
self.log(msg) self.log(msg)
self.reply(html.encode("utf-8"))
if not nullwrite: if not nullwrite:
# TODO this is bad # TODO this is bad
log_fn = "up.{:.6f}.txt".format(t0) log_fn = "up.{:.6f}.txt".format(t0)
@ -284,9 +296,21 @@ class HttpCli(object):
] ]
) )
+ "\n" + "\n"
+ errmsg
+ "\n"
).encode("utf-8") ).encode("utf-8")
) )
html = self.conn.tpl_msg.render(
h2='<a href="/{}">return to /{}</a>'.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): def tx_file(self, path):
sz = os.path.getsize(fsenc(path)) sz = os.path.getsize(fsenc(path))
mime = mimetypes.guess_type(path)[0] mime = mimetypes.guess_type(path)[0]
@ -296,11 +320,10 @@ class HttpCli(object):
"utf-8" "utf-8"
) )
if self.ok:
self.s.send(header) self.s.send(header)
with open(fsenc(path), "rb") as f: with open(fsenc(path), "rb") as f:
while self.ok: while True:
buf = f.read(4096) buf = f.read(4096)
if not buf: if not buf:
break break
@ -309,7 +332,6 @@ class HttpCli(object):
self.s.send(buf) self.s.send(buf)
except ConnectionResetError: except ConnectionResetError:
return False return False
# TODO propagate (self.ok or return)
def tx_mounts(self): def tx_mounts(self):
html = self.conn.tpl_mounts.render(this=self) html = self.conn.tpl_mounts.render(this=self)

View file

@ -24,7 +24,6 @@ class HttpConn(object):
self.sr = Unrecv(sck) self.sr = Unrecv(sck)
self.workload = 0 self.workload = 0
self.ok = True
self.log_func = log_func self.log_func = log_func
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26) self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26)

View file

@ -159,7 +159,7 @@ class MultipartParser(object):
buf = self.sr.recv(bufsz) buf = self.sr.recv(bufsz)
if not buf: if not buf:
# abort: client disconnected # abort: client disconnected
raise Exception("client disconnected during post") raise Pebkac("client disconnected during post")
while True: while True:
ofs = buf.find(self.boundary) ofs = buf.find(self.boundary)
@ -193,7 +193,7 @@ class MultipartParser(object):
buf2 = self.sr.recv(bufsz) buf2 = self.sr.recv(bufsz)
if not buf2: if not buf2:
# abort: client disconnected # abort: client disconnected
raise Exception("client disconnected during post") raise Pebkac("client disconnected during post")
buf += buf2 buf += buf2
@ -288,7 +288,13 @@ def read_header(sr):
buf = sr.recv(n) buf = sr.recv(n)
if not buf: 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 ret += buf