X-Git-Url: http://git.dolda2000.com/gitweb/?a=blobdiff_plain;ds=inline;f=wrw%2Fproto.py;h=2438c7c4f17c44b49a9fe7025b774ec21ed3b9d6;hb=HEAD;hp=96a88781ba5e7eda8267ae100ad17fc41e5e65f9;hpb=babf3e21cb06bf867e3ec98003d4a077090b8839;p=wrw.git diff --git a/wrw/proto.py b/wrw/proto.py index 96a8878..5a2fee7 100644 --- a/wrw/proto.py +++ b/wrw/proto.py @@ -1,4 +1,4 @@ -import time +import time, calendar, collections.abc, binascii, base64 statusinfo = { 400: ("Bad Request", "Invalid HTTP request."), @@ -6,9 +6,39 @@ statusinfo = { 403: ("Forbidden", "You are not authorized to request the requested resource."), 404: ("Not Found", "The requested resource was not found."), 405: ("Method Not Allowed", "The request method is not recognized or permitted by the requested resource."), + 406: ("Not Acceptable", "No way was found to satisfy the given content-negotiation criteria."), + 407: ("Proxy Authentication Required", "Authentication must be provided to proxy the request."), + 408: ("Request Timeout", "The connection timed out."), + 409: ("Conflict", "The request conflicts with the live state."), + 410: ("Gone", "The requested resource has been deleted."), + 411: ("Length Required", "The requested resource requires the Content-Length header."), + 412: ("Precondition Failed", "The preconditions specified in the request are not met."), + 413: ("Payload Too Large", "The request entity is larger than permitted."), + 414: ("URI Too Long", "The requested URI is too long."), + 415: ("Unsupported Media Type", "The request entity format is not supported."), + 416: ("Range Not Satisfiable", "The specified Range cannot be satisfied."), + 417: ("Expectation Failed", "The expectation specified by the Expect header cannot be met."), + 421: ("Misdirected Request", "This server cannot handle the request."), + 422: ("Unprocessable Content", "The requet entity cannot be processed."), + 423: ("Locked", "The requested resource is locked."), + 424: ("Failed Dependency", "A previous required request failed."), + 425: ("TOo Early", "The requested action has already been performed."), + 426: ("Upgrade Required", "The requested resource is not available over this protocol."), + 428: ("Precondition Requred", "The requested resource needs to be conditionally requested."), + 429: ("Too Many Requests", "Your client is sending more frequent requests than are accepted."), + 431: ("Request Header Fields Too Large", "The request headers are too large."), + 451: ("Unavilable For Legal Reasons", "The requested resource has been censored."), 500: ("Server Error", "An internal error occurred."), 501: ("Not Implemented", "The requested functionality has not been implemented."), + 502: ("Bad Gateway", "The backing server indicated an error."), 503: ("Service Unavailable", "Service is being denied at this time."), + 504: ("Gateway Timeout", "The backing server is not responding."), + 505: ("Unsupported HTTP Version", "The server does not support the requested HTTP version."), + 506: ("Variant Also Negotiates", "The server content-negotiation is misconfigured."), + 507: ("Insufficient Storage", "The server is out of storage to process the request."), + 508: ("Loop Detected", "An infinite loop was detected while processing the request."), + 510: ("Not Extended", "The requested extension is not supported."), + 511: ("Network Authentication Required", "Authentication for network access is required."), } def httpdate(ts): @@ -21,7 +51,7 @@ def phttpdate(dstr): return None tz = int(tz[1:]) tz = (((tz / 100) * 60) + (tz % 100)) * 60 - return time.mktime(time.strptime(dstr, "%a, %d %b %Y %H:%M:%S")) - tz - time.altzone + return calendar.timegm(time.strptime(dstr, "%a, %d %b %Y %H:%M:%S")) - tz def pmimehead(hstr): def pws(p): @@ -103,13 +133,15 @@ def simpleerror(env, startreq, code, title, msg): return [buf] def urlq(url): + if isinstance(url, str): + url = url.encode("utf-8") ret = "" - invalid = "&=#?/\"'" + invalid = b"%;&=+#?/\"'" for c in url: - if c in invalid or (ord(c) <= 32): - ret += "%%%02X" % ord(c) + if c in invalid or (c <= 32) or (c >= 128): + ret += "%%%02X" % c else: - ret += c + ret += chr(c) return ret class urlerror(ValueError): @@ -166,11 +198,16 @@ def scripturl(req): raise Exception("Malformed local part when reconstructing URL") return siteurl(req) + req.uriname[1:] -def requrl(req): +def requrl(req, qs=True): s = siteurl(req) if req.uri[0] != '/': raise Exception("Malformed local part when reconstructing URL") - return siteurl(req) + req.uri[1:] + pf = req.uri[1:] + if not qs: + p = pf.find('?') + if not p < 0: + pf = pf[:p] + return siteurl(req) + pf def parstring(pars={}, **augment): buf = "" @@ -180,16 +217,135 @@ def parstring(pars={}, **augment): del augment[key] else: val = pars[key] + if val is None: + continue if buf != "": buf += "&" buf += urlq(key) + "=" + urlq(str(val)) - for key in augment: + for key, val in augment.items(): + if val is None: + continue if buf != "": buf += "&" - buf += urlq(key) + "=" + urlq(str(augment[key])) + buf += urlq(key) + "=" + urlq(str(val)) return buf def parurl(url, pars={}, **augment): qs = parstring(pars, **augment) if qs != "": - return url + "?" + qs + return url + ("&" if "?" in url else "?") + qs else: return url + +# Wrap these, since binascii is a bit funky. :P +def enhex(bs): + return base64.b16encode(bs).decode("us-ascii") +def unhex(es): + if not isinstance(es, collections.abc.ByteString): + try: + es = es.encode("us-ascii") + except UnicodeError: + raise binascii.Error("non-ascii character in hex-string") + return base64.b16decode(es) +def enb32(bs): + return base64.b32encode(bs).decode("us-ascii") +def unb32(es): + if not isinstance(es, collections.abc.ByteString): + try: + es = es.encode("us-ascii") + except UnicodeError: + raise binascii.Error("non-ascii character in base32-string") + if (len(es) % 8) != 0: + es += b"=" * (8 - (len(es) % 8)) + es = es.upper() # The whole point of Base32 is that it's case-insensitive :P + return base64.b32decode(es) +def enb64(bs): + return base64.b64encode(bs).decode("us-ascii") +def unb64(es): + if not isinstance(es, collections.abc.ByteString): + try: + es = es.encode("us-ascii") + except UnicodeError: + raise binascii.Error("non-ascii character in base64-string") + if (len(es) % 4) != 0: + es += b"=" * (4 - (len(es) % 4)) + return base64.b64decode(es) + +def _quoprisafe(): + ret = [False] * 256 + for c in "-!*+/": + ret[ord(c)] = True + for c in range(ord('0'), ord('9') + 1): + ret[c] = True + for c in range(ord('A'), ord('Z') + 1): + ret[c] = True + for c in range(ord('a'), ord('z') + 1): + ret[c] = True + return ret +_quoprisafe = _quoprisafe() +def quopri(s, charset="utf-8"): + bv = s.encode(charset) + qn = sum(not _quoprisafe[b] for b in bv) + if qn == 0: + return s + if qn > len(bv) / 2: + return "=?%s?B?%s?=" % (charset, enb64(bv)) + else: + return "=?%s?Q?%s?=" % (charset, "".join(chr(b) if _quoprisafe[b] else "=%02X" % b for b in bv)) + +class mimeparam(object): + def __init__(self, name, val, fallback=None, charset="utf-8", lang=""): + self.name = name + self.val = val + self.fallback = fallback + self.charset = charset + self.lang = lang + + def __str__(self): + self.name.encode("ascii") + try: + self.val.encode("ascii") + except UnicodeError: + pass + else: + return "%s=%s" % (self.name, self.val) + val = self.val.encode(self.charset) + self.charset.encode("ascii") + self.lang.encode("ascii") + ret = "" + if self.fallback is not None: + self.fallback.encode("ascii") + ret += "%s=%s; " % (self.name, self.fallback) + ret += "%s*=%s'%s'%s" % (self.name, self.charset, self.lang, urlq(val)) + return ret + +class mimeheader(object): + def __init__(self, name, val, *, mime_charset="utf-8", mime_lang="", **params): + self.name = name + self.val = val + self.params = {} + self.charset = mime_charset + self.lang = mime_lang + for k, v in params.items(): + self[k] = v + + def __getitem__(self, nm): + return self.params[nm.lower()] + + def __setitem__(self, nm, val): + if not isinstance(val, mimeparam): + val = mimeparam(nm, val, charset=self.charset, lang=self.lang) + self.params[nm.lower()] = val + + def __delitem__(self, nm): + del self.params[nm.lower()] + + def value(self): + parts = [] + if self.val != None: + parts.append(quopri(self.val)) + parts.extend(str(x) for x in self.params.values()) + return("; ".join(parts)) + + def __str__(self): + if self.name is None: + return self.value() + return "%s: %s" % (self.name, self.value())