Added a simple function for producing an optionally parametrized URL.
[wrw.git] / wrw / proto.py
1 statusinfo = {
2     400: ("Bad Request", "Invalid HTTP request."),
3     401: ("Unauthorized", "Authentication must be provided for the requested resource."),
4     403: ("Forbidden", "You are not authorized to request the requested resource."),
5     404: ("Not Found", "The requested resource was not found."),
6     405: ("Method Not Allowed", "The request method is not recognized or permitted by the requested resource."),
7     500: ("Server Error", "An internal error occurred."),
8     501: ("Not Implemented", "The requested functionality has not been implemented."),
9     503: ("Service Unavailable", "Service is being denied at this time."),
10     }
11
12 def httpdate(ts):
13     return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(ts))
14
15 def phttpdate(dstr):
16     tz = dstr[-6:]
17     dstr = dstr[:-6]
18     if tz[0] != " " or (tz[1] != "+" and tz[1] != "-") or not tz[2:].isdigit():
19         return None
20     tz = int(tz[1:])
21     tz = (((tz / 100) * 60) + (tz % 100)) * 60
22     return time.mktime(time.strptime(dstr, "%a, %d %b %Y %H:%M:%S")) - tz - time.altzone
23
24 def pmimehead(hstr):
25     def pws(p):
26         while p < len(hstr) and hstr[p].isspace():
27             p += 1
28         return p
29     def token(p, sep):
30         buf = ""
31         p = pws(p)
32         if p >= len(hstr):
33             return "", p
34         if hstr[p] == '"':
35             p += 1
36             while p < len(hstr):
37                 if hstr[p] == '\\':
38                     p += 1
39                     if p < len(hstr):
40                         buf += hstr[p]
41                         p += 1
42                     else:
43                         break
44                 elif hstr[p] == '"':
45                     p += 1
46                     break
47                 else:
48                     buf += hstr[p]
49                     p += 1
50             return buf, pws(p)
51         else:
52             while p < len(hstr):
53                 if hstr[p] in sep:
54                     break
55                 buf += hstr[p]
56                 p += 1
57             return buf.strip(), pws(p)
58     p = 0
59     val, p = token(p, ";")
60     pars = {}
61     while p < len(hstr):
62         if hstr[p] != ';':
63             break
64         p += 1
65         k, p = token(p, "=")
66         if k == "" or hstr[p:p + 1] != '=':
67             break
68         p += 1
69         v, p = token(p, ';')
70         pars[k.lower()] = v
71     return val, pars
72
73 def htmlq(html):
74     ret = ""
75     for c in html:
76         if c == "&":
77             ret += "&amp;"
78         elif c == "<":
79             ret += "&lt;"
80         elif c == ">":
81             ret += "&gt;"
82         else:
83             ret += c
84     return ret
85
86 def urlq(url):
87     ret = ""
88     for c in url:
89         if c == "&" or c == "=" or c == "#" or c == "?" or c == "/" or (ord(c) <= 32):
90             ret += "%%%02X" % ord(c)
91         else:
92             ret += c
93     return ret
94
95 class urlerror(ValueError):
96     pass
97
98 def parseurl(url):
99     p = url.find("://")
100     if p < 0:
101         raise urlerror("Protocol not found in absolute URL `%s'" % url)
102     proto = url[:p]
103     l = url.find("/", p + 3)
104     if l < 0:
105         raise urlerror("Local part not found in absolute URL `%s'" % url)
106     host = url[p + 3:l]
107     local = url[l:]
108     q = local.find("?")
109     if q < 0:
110         query = ""
111     else:
112         query = local[q + 1:]
113         local = local[:q]
114     return proto, host, local, query
115
116 def consurl(proto, host, local, query = ""):
117     if len(local) < 1 and local[0] != '/':
118         raise urlerror("Local part of URL must begin with a slash")
119     ret = "%s://%s%s" % (proto, host, local)
120     if len(query) > 0:
121         ret += "?" + query
122     return ret
123
124 def appendurl(url, other):
125     if "://" in other:
126         return other
127     proto, host, local, query = parseurl(url)
128     if len(other) > 0 and other[0] == '/':
129         return consurl(proto, host, other)
130     else:
131         p = local.rfind('/')
132         return consurl(proto, host, local[:p + 1] + other)
133
134 def requrl(req):
135     host = req.ihead.get("Host", None)
136     if host is None:
137         raise Exception("Could not reconstruct URL because no Host header was sent")
138     proto = "http"
139     if req.https:
140         proto = "https"
141     if req.uri[0] != '/':
142         raise Exception("Malformed local part when reconstructing URL")
143     return "%s://%s%s" % (proto, host, req.uri)
144
145 def parstring(pars = {}, **augment):
146     buf = ""
147     for key in pars:
148         if key in augment:
149             val = augment[key]
150             del augment[key]
151         else:
152             val = pars[key]
153         if buf != "": buf += "&"
154         buf += urlq(key) + "=" + urlq(str(val))
155     for key in augment:
156         if buf != "": buf += "&"
157         buf += urlq(key) + "=" + urlq(str(augment[key]))
158     return buf
159
160 def parurl(url, pars = {}, **augment):
161     qs = parstring(pars, **augment)
162     if qs != "":
163         return url + "?" + qs
164     else:
165         return url