http error codes

This commit is contained in:
ed 2019-06-26 23:32:34 +00:00
parent f110d1254d
commit 96c6be0ea1
3 changed files with 57 additions and 37 deletions

View file

@ -80,10 +80,10 @@ class VFS(object):
vn, rem = self._find(vpath)
if will_read and (uname not in vn.uread and "*" not in vn.uread):
raise Pebkac("you don't have read-access for this location")
raise Pebkac(403, "you don't have read-access for this location")
if will_write and (uname not in vn.uwrite and "*" not in vn.uwrite):
raise Pebkac("you don't have write-access for this location")
raise Pebkac(403, "you don't have write-access for this location")
return vn, rem

View file

@ -50,10 +50,10 @@ class HttpCli(object):
try:
self.mode, self.req, _ = headerlines[0].split(" ")
except:
raise Pebkac("bad headers:\n" + "\n".join(headerlines))
raise Pebkac(400, "bad headers:\n" + "\n".join(headerlines))
except Pebkac as ex:
self.loud_reply(str(ex))
self.loud_reply(str(ex), status=ex.code)
return False
self.headers = {}
@ -107,20 +107,20 @@ class HttpCli(object):
elif self.mode == "POST":
return self.handle_post()
else:
raise Pebkac('invalid HTTP mode "{0}"'.format(self.mode))
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
except Pebkac as ex:
try:
self.loud_reply(str(ex))
self.loud_reply(str(ex), status=ex.code)
except Pebkac:
pass
return False
def reply(self, body, status="200 OK", mime="text/html", headers=[]):
def reply(self, body, status=200, mime="text/html", headers=[]):
# TODO something to reply with user-supplied values safely
response = [
"HTTP/1.1 " + status,
"HTTP/1.1 {} {}".format(status, HTTPCODE[status]),
"Connection: Keep-Alive",
"Content-Type: " + mime,
"Content-Length: " + str(len(body)),
@ -133,7 +133,7 @@ class HttpCli(object):
try:
self.s.sendall(response_str + b"\r\n\r\n" + body)
except:
raise Pebkac("client disconnected before http response")
raise Pebkac(400, "client disconnected before http response")
return body
@ -148,7 +148,7 @@ class HttpCli(object):
try:
rval = self.headers["range"].split("=", 1)[1]
except:
rval += self.headers["range"]
rval = self.headers["range"]
logmsg += " [\033[36m" + rval + "\033[0m]"
@ -196,7 +196,7 @@ class HttpCli(object):
ctype = self.headers.get("content-type", "").lower()
if not ctype:
raise Pebkac("you can't post without a content-type header")
raise Pebkac(400, "you can't post without a content-type header")
if "multipart/form-data" in ctype:
return self.handle_post_multipart()
@ -207,7 +207,7 @@ class HttpCli(object):
if "application/octet-stream" in ctype:
return self.handle_post_binary()
raise Pebkac("don't know how to handle {} POST".format(ctype))
raise Pebkac(405, "don't know how to handle {} POST".format(ctype))
def handle_post_multipart(self):
self.parser = MultipartParser(self.log, self.sr, self.headers)
@ -221,16 +221,16 @@ class HttpCli(object):
if act == "login":
return self.handle_login()
raise Pebkac('invalid action "{}"'.format(act))
raise Pebkac(422, 'invalid action "{}"'.format(act))
def handle_post_json(self):
try:
remains = int(self.headers["content-length"])
except:
raise Pebkac("you must supply a content-length for JSON POST")
raise Pebkac(400, "you must supply a content-length for JSON POST")
if remains > 1024 * 1024:
raise Pebkac("json 2big")
raise Pebkac(413, "json 2big")
enc = "utf-8"
ctype = self.headers.get("content-type", "").lower()
@ -245,7 +245,7 @@ class HttpCli(object):
try:
body = json.loads(json_buf.decode(enc, "replace"))
except:
raise Pebkac("you POSTed invalid json")
raise Pebkac(422, "you POSTed invalid json")
print(body)
@ -291,7 +291,7 @@ class HttpCli(object):
fn = os.path.join(fdir, sanitize_fn(p_file))
if not os.path.isdir(fsenc(fdir)):
raise Pebkac("that folder does not exist")
raise Pebkac(404, "that folder does not exist")
# TODO broker which avoid this race
# and provides a new filename if taken
@ -303,7 +303,7 @@ class HttpCli(object):
self.log("writing to {0}".format(fn))
sz, sha512 = hashcopy(self.conn, p_data, f)
if sz == 0:
raise Pebkac("empty files in post")
raise Pebkac(400, "empty files in post")
files.append([sz, sha512])
@ -365,9 +365,9 @@ class HttpCli(object):
def tx_file(self, req_path):
do_send = True
status = "200 OK"
status = 200
extra_headers = []
logmsg = "{:4} {} {}".format("", self.req, status)
logmsg = "{:4} {} {}".format("", self.req, "200 OK")
#
# if request is for foo.js, check if we have foo.js.gz
@ -382,7 +382,7 @@ class HttpCli(object):
try:
file_sz = os.path.getsize(fsenc(fs_path))
except:
raise Pebkac("404 Not Found")
raise Pebkac(404)
#
# if-modified
@ -402,7 +402,7 @@ class HttpCli(object):
do_send = file_lastmod != cli_lastmod
if not do_send:
status = "304 Not Modified"
status = 304
#
# partial
@ -426,12 +426,12 @@ class HttpCli(object):
upper = file_sz
if lower < 0 or lower >= file_sz or upper < 0 or upper > file_sz:
raise Pebkac("na")
raise Exception()
except:
self.loud_reply("invalid range requested: " + hrange)
raise Pebkac(400, "invalid range requested: " + hrange)
status = "206 Partial Content"
status = 206
extra_headers.append(
"Content-Range: bytes {}-{}/{}".format(lower, upper - 1, file_sz)
)
@ -469,7 +469,7 @@ class HttpCli(object):
mime = mimetypes.guess_type(req_path)[0] or "application/octet-stream"
headers = [
"HTTP/1.1 " + status,
"HTTP/1.1 {} {}".format(status, HTTPCODE[status]),
"Connection: Keep-Alive",
"Content-Type: " + mime,
"Content-Length: " + str(upper - lower),
@ -535,7 +535,7 @@ class HttpCli(object):
if not os.path.exists(fsenc(abspath)):
print(abspath)
raise Pebkac("404 not found")
raise Pebkac(404)
if not os.path.isdir(fsenc(abspath)):
return self.tx_file(abspath)

View file

@ -24,6 +24,21 @@ surrogateescape.register_surrogateescape()
FS_ENCODING = sys.getfilesystemencoding()
HTTPCODE = {
200: "OK",
206: "Partial Content",
304: "Not Modified",
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
413: "Payload Too Large",
422: "Unprocessable Entity",
500: "Internal Server Error",
501: "Not Implemented",
}
class Counter(object):
def __init__(self, v=0):
self.v = v
@ -104,12 +119,12 @@ class MultipartParser(object):
continue
if m.group(1).lower() != "form-data":
raise Pebkac("not form-data: {}".format(ln))
raise Pebkac(400, "not form-data: {}".format(ln))
try:
field = self.re_cdisp_field.match(ln).group(1)
except:
raise Pebkac("missing field name: {}".format(ln))
raise Pebkac(400, "missing field name: {}".format(ln))
try:
fn = self.re_cdisp_file.match(ln).group(1)
@ -160,7 +175,7 @@ class MultipartParser(object):
buf = self.sr.recv(bufsz)
if not buf:
# abort: client disconnected
raise Pebkac("client disconnected during post")
raise Pebkac(400, "client disconnected during post")
while True:
ofs = buf.find(self.boundary)
@ -194,7 +209,7 @@ class MultipartParser(object):
buf2 = self.sr.recv(bufsz)
if not buf2:
# abort: client disconnected
raise Pebkac("client disconnected during post")
raise Pebkac(400, "client disconnected during post")
buf += buf2
@ -217,14 +232,14 @@ class MultipartParser(object):
return
if tail != b"\r\n":
raise Pebkac("protocol error after field value")
raise Pebkac(400, "protocol error after field value")
def _read_value(self, iterator, max_len):
ret = b""
for buf in iterator:
ret += buf
if len(ret) > max_len:
raise Pebkac("field length is too long")
raise Pebkac(400, "field length is too long")
return ret
@ -250,7 +265,9 @@ class MultipartParser(object):
"""
p_field, _, p_data = next(self.gen)
if p_field != field_name:
raise Pebkac('expected field "{}", got "{}"'.format(field_name, p_field))
raise Pebkac(
422, 'expected field "{}", got "{}"'.format(field_name, p_field)
)
return self._read_value(p_data, max_len).decode("utf-8", "surrogateescape")
@ -268,7 +285,7 @@ def get_boundary(headers):
ct = headers["content-type"]
m = re.match(ptn, ct, re.IGNORECASE)
if not m:
raise Pebkac("invalid content-type for a multipart post: {}".format(ct))
raise Pebkac(400, "invalid content-type for a multipart post: {}".format(ct))
return m.group(2)
@ -293,8 +310,9 @@ def read_header(sr):
return None
raise Pebkac(
400,
"protocol error while reading headers:\n"
+ ret.decode("utf-8", "replace")
+ ret.decode("utf-8", "replace"),
)
ret += buf
@ -424,4 +442,6 @@ def gzip_orig_sz(fn):
class Pebkac(Exception):
pass
def __init__(self, code, msg=None):
super(Pebkac, self).__init__(msg or HTTPCODE[code])
self.code = code