mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
http error codes
This commit is contained in:
parent
f110d1254d
commit
96c6be0ea1
|
@ -80,10 +80,10 @@ class VFS(object):
|
||||||
vn, rem = self._find(vpath)
|
vn, rem = self._find(vpath)
|
||||||
|
|
||||||
if will_read and (uname not in vn.uread and "*" not in vn.uread):
|
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):
|
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
|
return vn, rem
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,10 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
self.mode, self.req, _ = headerlines[0].split(" ")
|
self.mode, self.req, _ = headerlines[0].split(" ")
|
||||||
except:
|
except:
|
||||||
raise Pebkac("bad headers:\n" + "\n".join(headerlines))
|
raise Pebkac(400, "bad headers:\n" + "\n".join(headerlines))
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
self.loud_reply(str(ex))
|
self.loud_reply(str(ex), status=ex.code)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
@ -107,20 +107,20 @@ class HttpCli(object):
|
||||||
elif self.mode == "POST":
|
elif self.mode == "POST":
|
||||||
return self.handle_post()
|
return self.handle_post()
|
||||||
else:
|
else:
|
||||||
raise Pebkac('invalid HTTP mode "{0}"'.format(self.mode))
|
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
try:
|
try:
|
||||||
self.loud_reply(str(ex))
|
self.loud_reply(str(ex), status=ex.code)
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return False
|
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
|
# TODO something to reply with user-supplied values safely
|
||||||
response = [
|
response = [
|
||||||
"HTTP/1.1 " + status,
|
"HTTP/1.1 {} {}".format(status, HTTPCODE[status]),
|
||||||
"Connection: Keep-Alive",
|
"Connection: Keep-Alive",
|
||||||
"Content-Type: " + mime,
|
"Content-Type: " + mime,
|
||||||
"Content-Length: " + str(len(body)),
|
"Content-Length: " + str(len(body)),
|
||||||
|
@ -133,7 +133,7 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
self.s.sendall(response_str + b"\r\n\r\n" + body)
|
self.s.sendall(response_str + b"\r\n\r\n" + body)
|
||||||
except:
|
except:
|
||||||
raise Pebkac("client disconnected before http response")
|
raise Pebkac(400, "client disconnected before http response")
|
||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
rval = self.headers["range"].split("=", 1)[1]
|
rval = self.headers["range"].split("=", 1)[1]
|
||||||
except:
|
except:
|
||||||
rval += self.headers["range"]
|
rval = self.headers["range"]
|
||||||
|
|
||||||
logmsg += " [\033[36m" + rval + "\033[0m]"
|
logmsg += " [\033[36m" + rval + "\033[0m]"
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
ctype = self.headers.get("content-type", "").lower()
|
ctype = self.headers.get("content-type", "").lower()
|
||||||
if not ctype:
|
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:
|
if "multipart/form-data" in ctype:
|
||||||
return self.handle_post_multipart()
|
return self.handle_post_multipart()
|
||||||
|
@ -207,7 +207,7 @@ class HttpCli(object):
|
||||||
if "application/octet-stream" in ctype:
|
if "application/octet-stream" in ctype:
|
||||||
return self.handle_post_binary()
|
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):
|
def handle_post_multipart(self):
|
||||||
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
||||||
|
@ -221,16 +221,16 @@ class HttpCli(object):
|
||||||
if act == "login":
|
if act == "login":
|
||||||
return self.handle_login()
|
return self.handle_login()
|
||||||
|
|
||||||
raise Pebkac('invalid action "{}"'.format(act))
|
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
||||||
|
|
||||||
def handle_post_json(self):
|
def handle_post_json(self):
|
||||||
try:
|
try:
|
||||||
remains = int(self.headers["content-length"])
|
remains = int(self.headers["content-length"])
|
||||||
except:
|
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:
|
if remains > 1024 * 1024:
|
||||||
raise Pebkac("json 2big")
|
raise Pebkac(413, "json 2big")
|
||||||
|
|
||||||
enc = "utf-8"
|
enc = "utf-8"
|
||||||
ctype = self.headers.get("content-type", "").lower()
|
ctype = self.headers.get("content-type", "").lower()
|
||||||
|
@ -245,7 +245,7 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
body = json.loads(json_buf.decode(enc, "replace"))
|
body = json.loads(json_buf.decode(enc, "replace"))
|
||||||
except:
|
except:
|
||||||
raise Pebkac("you POSTed invalid json")
|
raise Pebkac(422, "you POSTed invalid json")
|
||||||
|
|
||||||
print(body)
|
print(body)
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ class HttpCli(object):
|
||||||
fn = os.path.join(fdir, sanitize_fn(p_file))
|
fn = os.path.join(fdir, sanitize_fn(p_file))
|
||||||
|
|
||||||
if not os.path.isdir(fsenc(fdir)):
|
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
|
# TODO broker which avoid this race
|
||||||
# and provides a new filename if taken
|
# and provides a new filename if taken
|
||||||
|
@ -303,7 +303,7 @@ class HttpCli(object):
|
||||||
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:
|
||||||
raise Pebkac("empty files in post")
|
raise Pebkac(400, "empty files in post")
|
||||||
|
|
||||||
files.append([sz, sha512])
|
files.append([sz, sha512])
|
||||||
|
|
||||||
|
@ -365,9 +365,9 @@ class HttpCli(object):
|
||||||
|
|
||||||
def tx_file(self, req_path):
|
def tx_file(self, req_path):
|
||||||
do_send = True
|
do_send = True
|
||||||
status = "200 OK"
|
status = 200
|
||||||
extra_headers = []
|
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
|
# if request is for foo.js, check if we have foo.js.gz
|
||||||
|
@ -382,7 +382,7 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
file_sz = os.path.getsize(fsenc(fs_path))
|
file_sz = os.path.getsize(fsenc(fs_path))
|
||||||
except:
|
except:
|
||||||
raise Pebkac("404 Not Found")
|
raise Pebkac(404)
|
||||||
|
|
||||||
#
|
#
|
||||||
# if-modified
|
# if-modified
|
||||||
|
@ -402,7 +402,7 @@ class HttpCli(object):
|
||||||
do_send = file_lastmod != cli_lastmod
|
do_send = file_lastmod != cli_lastmod
|
||||||
|
|
||||||
if not do_send:
|
if not do_send:
|
||||||
status = "304 Not Modified"
|
status = 304
|
||||||
|
|
||||||
#
|
#
|
||||||
# partial
|
# partial
|
||||||
|
@ -426,12 +426,12 @@ class HttpCli(object):
|
||||||
upper = file_sz
|
upper = file_sz
|
||||||
|
|
||||||
if lower < 0 or lower >= file_sz or upper < 0 or upper > file_sz:
|
if lower < 0 or lower >= file_sz or upper < 0 or upper > file_sz:
|
||||||
raise Pebkac("na")
|
raise Exception()
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.loud_reply("invalid range requested: " + hrange)
|
raise Pebkac(400, "invalid range requested: " + hrange)
|
||||||
|
|
||||||
status = "206 Partial Content"
|
status = 206
|
||||||
extra_headers.append(
|
extra_headers.append(
|
||||||
"Content-Range: bytes {}-{}/{}".format(lower, upper - 1, file_sz)
|
"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"
|
mime = mimetypes.guess_type(req_path)[0] or "application/octet-stream"
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
"HTTP/1.1 " + status,
|
"HTTP/1.1 {} {}".format(status, HTTPCODE[status]),
|
||||||
"Connection: Keep-Alive",
|
"Connection: Keep-Alive",
|
||||||
"Content-Type: " + mime,
|
"Content-Type: " + mime,
|
||||||
"Content-Length: " + str(upper - lower),
|
"Content-Length: " + str(upper - lower),
|
||||||
|
@ -535,7 +535,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
if not os.path.exists(fsenc(abspath)):
|
if not os.path.exists(fsenc(abspath)):
|
||||||
print(abspath)
|
print(abspath)
|
||||||
raise Pebkac("404 not found")
|
raise Pebkac(404)
|
||||||
|
|
||||||
if not os.path.isdir(fsenc(abspath)):
|
if not os.path.isdir(fsenc(abspath)):
|
||||||
return self.tx_file(abspath)
|
return self.tx_file(abspath)
|
||||||
|
|
|
@ -24,6 +24,21 @@ surrogateescape.register_surrogateescape()
|
||||||
FS_ENCODING = sys.getfilesystemencoding()
|
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):
|
class Counter(object):
|
||||||
def __init__(self, v=0):
|
def __init__(self, v=0):
|
||||||
self.v = v
|
self.v = v
|
||||||
|
@ -104,12 +119,12 @@ class MultipartParser(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if m.group(1).lower() != "form-data":
|
if m.group(1).lower() != "form-data":
|
||||||
raise Pebkac("not form-data: {}".format(ln))
|
raise Pebkac(400, "not form-data: {}".format(ln))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
field = self.re_cdisp_field.match(ln).group(1)
|
field = self.re_cdisp_field.match(ln).group(1)
|
||||||
except:
|
except:
|
||||||
raise Pebkac("missing field name: {}".format(ln))
|
raise Pebkac(400, "missing field name: {}".format(ln))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fn = self.re_cdisp_file.match(ln).group(1)
|
fn = self.re_cdisp_file.match(ln).group(1)
|
||||||
|
@ -160,7 +175,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 Pebkac("client disconnected during post")
|
raise Pebkac(400, "client disconnected during post")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
ofs = buf.find(self.boundary)
|
ofs = buf.find(self.boundary)
|
||||||
|
@ -194,7 +209,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 Pebkac("client disconnected during post")
|
raise Pebkac(400, "client disconnected during post")
|
||||||
|
|
||||||
buf += buf2
|
buf += buf2
|
||||||
|
|
||||||
|
@ -217,14 +232,14 @@ class MultipartParser(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
if tail != b"\r\n":
|
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):
|
def _read_value(self, iterator, max_len):
|
||||||
ret = b""
|
ret = b""
|
||||||
for buf in iterator:
|
for buf in iterator:
|
||||||
ret += buf
|
ret += buf
|
||||||
if len(ret) > max_len:
|
if len(ret) > max_len:
|
||||||
raise Pebkac("field length is too long")
|
raise Pebkac(400, "field length is too long")
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -250,7 +265,9 @@ class MultipartParser(object):
|
||||||
"""
|
"""
|
||||||
p_field, _, p_data = next(self.gen)
|
p_field, _, p_data = next(self.gen)
|
||||||
if p_field != field_name:
|
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")
|
return self._read_value(p_data, max_len).decode("utf-8", "surrogateescape")
|
||||||
|
|
||||||
|
@ -268,7 +285,7 @@ def get_boundary(headers):
|
||||||
ct = headers["content-type"]
|
ct = headers["content-type"]
|
||||||
m = re.match(ptn, ct, re.IGNORECASE)
|
m = re.match(ptn, ct, re.IGNORECASE)
|
||||||
if not m:
|
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)
|
return m.group(2)
|
||||||
|
|
||||||
|
@ -293,8 +310,9 @@ def read_header(sr):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
raise Pebkac(
|
raise Pebkac(
|
||||||
|
400,
|
||||||
"protocol error while reading headers:\n"
|
"protocol error while reading headers:\n"
|
||||||
+ ret.decode("utf-8", "replace")
|
+ ret.decode("utf-8", "replace"),
|
||||||
)
|
)
|
||||||
|
|
||||||
ret += buf
|
ret += buf
|
||||||
|
@ -424,4 +442,6 @@ def gzip_orig_sz(fn):
|
||||||
|
|
||||||
|
|
||||||
class Pebkac(Exception):
|
class Pebkac(Exception):
|
||||||
pass
|
def __init__(self, code, msg=None):
|
||||||
|
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
||||||
|
self.code = code
|
||||||
|
|
Loading…
Reference in a new issue