Added option to strip querystring from requrl.
[wrw.git] / wrw / proto.py
index 881f4e4..80e2419 100644 (file)
@@ -1,3 +1,5 @@
+import time, calendar, collections, binascii, base64
+
 statusinfo = {
     400: ("Bad Request", "Invalid HTTP request."),
     401: ("Unauthorized", "Authentication must be provided for the requested resource."),
@@ -19,7 +21,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):
@@ -83,13 +85,33 @@ def htmlq(html):
             ret += c
     return ret
 
+def simpleerror(env, startreq, code, title, msg):
+    buf = """<?xml version="1.0" encoding="US-ASCII"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
+<head>
+<title>%s</title>
+</head>
+<body>
+<h1>%s</h1>
+<p>%s</p>
+</body>
+</html>
+""" % (title, title, htmlq(msg))
+    buf = buf.encode("us-ascii")
+    startreq("%i %s" % (code, title), [("Content-Type", "text/html"), ("Content-Length", str(len(buf)))])
+    return [buf]
+
 def urlq(url):
+    if isinstance(url, str):
+        url = url.encode("utf-8")
     ret = ""
+    invalid = b"%;&=#?/\"'"
     for c in url:
-        if c == "&" or c == "=" or c == "#" or c == "?" or c == "/" 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):
@@ -113,7 +135,7 @@ def parseurl(url):
         local = local[:q]
     return proto, host, local, query
 
-def consurl(proto, host, local, query = ""):
+def consurl(proto, host, local, query=""):
     if len(local) < 1 and local[0] != '/':
         raise urlerror("Local part of URL must begin with a slash")
     ret = "%s://%s%s" % (proto, host, local)
@@ -131,18 +153,33 @@ def appendurl(url, other):
         p = local.rfind('/')
         return consurl(proto, host, local[:p + 1] + other)
 
-def requrl(req):
+def siteurl(req):
     host = req.ihead.get("Host", None)
     if host is None:
         raise Exception("Could not reconstruct URL because no Host header was sent")
     proto = "http"
     if req.https:
         proto = "https"
+    return "%s://%s/" % (proto, host)
+
+def scripturl(req):
+    s = siteurl(req)
+    if req.uriname[0] != '/':
+        raise Exception("Malformed local part when reconstructing URL")
+    return siteurl(req) + req.uriname[1:]
+
+def requrl(req, qs=True):
+    s = siteurl(req)
     if req.uri[0] != '/':
         raise Exception("Malformed local part when reconstructing URL")
-    return "%s://%s%s" % (proto, host, req.uri)
+    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):
+def parstring(pars={}, **augment):
     buf = ""
     for key in pars:
         if key in augment:
@@ -156,3 +193,44 @@ def parstring(pars = {}, **augment):
         if buf != "": buf += "&"
         buf += urlq(key) + "=" + urlq(str(augment[key]))
     return buf
+
+def parurl(url, pars={}, **augment):
+    qs = parstring(pars, **augment)
+    if 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.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.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.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)