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