1 """HTTP library functions."""
2
3
4
5
6
7
8
9 from BaseHTTPServer import BaseHTTPRequestHandler
10 response_codes = BaseHTTPRequestHandler.responses.copy()
11
12
13 response_codes[500] = ('Internal Server Error',
14 'The server encountered an unexpected condition '
15 'which prevented it from fulfilling the request.')
16 response_codes[503] = ('Service Unavailable',
17 'The server is currently unable to handle the '
18 'request due to a temporary overloading or '
19 'maintenance of the server.')
20
21
22 import cgi
23 import re
24 from rfc822 import formatdate as HTTPDate
25
26
28 """Return the given path *atoms, joined into a single URL.
29
30 This will correctly join a SCRIPT_NAME and PATH_INFO into the
31 original URL, even if either atom is blank.
32 """
33 url = "/".join([x for x in atoms if x])
34 while "//" in url:
35 url = url.replace("//", "/")
36
37 return url or "/"
38
40 """Return a protocol tuple from the given 'HTTP/x.y' string."""
41 return int(protocol_str[5]), int(protocol_str[7])
42
44 """Return a list of (start, stop) indices from a Range header, or None.
45
46 Each (start, stop) tuple will be composed of two ints, which are suitable
47 for use in a slicing operation. That is, the header "Range: bytes=3-6",
48 if applied against a Python string, is requesting resource[3:7]. This
49 function will return the list [(3, 7)].
50
51 If this function returns an empty list, you should return HTTP 416.
52 """
53
54 if not headervalue:
55 return None
56
57 result = []
58 bytesunit, byteranges = headervalue.split("=", 1)
59 for brange in byteranges.split(","):
60 start, stop = [x.strip() for x in brange.split("-", 1)]
61 if start:
62 if not stop:
63 stop = content_length - 1
64 start, stop = map(int, (start, stop))
65 if start >= content_length:
66
67
68
69
70
71
72
73
74 continue
75 if stop < start:
76
77
78
79
80
81
82 return None
83 result.append((start, stop + 1))
84 else:
85 if not stop:
86
87 return None
88
89 result.append((content_length - int(stop), content_length))
90
91 return result
92
93
95 """An element (with parameters) from an HTTP header's element list."""
96
102
104 p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()]
105 return u"%s%s" % (self.value, "".join(p))
106
109
111 """Transform 'token;key=val' to ('token', {'key': 'val'})."""
112
113
114 atoms = [x.strip() for x in elementstr.split(";") if x.strip()]
115 initial_value = atoms.pop(0).strip()
116 params = {}
117 for atom in atoms:
118 atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
119 key = atom.pop(0)
120 if atom:
121 val = atom[0]
122 else:
123 val = ""
124 params[key] = val
125 return initial_value, params
126 parse = staticmethod(parse)
127
129 """Construct an instance from a string of the form 'token;key=val'."""
130 ival, params = cls.parse(elementstr)
131 return cls(ival, params)
132 from_str = classmethod(from_str)
133
134
135 q_separator = re.compile(r'; *q *=')
136
138 """An element (with parameters) from an Accept* header's element list.
139
140 AcceptElement objects are comparable; the more-preferred object will be
141 "less than" the less-preferred object. They are also therefore sortable;
142 if you sort a list of AcceptElement objects, they will be listed in
143 priority order; the most preferred value will be first. Yes, it should
144 have been the other way around, but it's too late to fix now.
145 """
146
162 from_str = classmethod(from_str)
163
165 val = self.params.get("q", "1")
166 if isinstance(val, HeaderElement):
167 val = val.value
168 return float(val)
169 qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
170
172 diff = cmp(other.qvalue, self.qvalue)
173 if diff == 0:
174 diff = cmp(str(other), str(self))
175 return diff
176
177
179 """Return a HeaderElement list from a comma-separated header str."""
180
181 if not fieldvalue:
182 return None
183 headername = fieldname.lower()
184
185 result = []
186 for element in fieldvalue.split(","):
187 if headername.startswith("accept") or headername == 'te':
188 hv = AcceptElement.from_str(element)
189 else:
190 hv = HeaderElement.from_str(element)
191 result.append(hv)
192
193 result.sort()
194 return result
195
196 -def decode_TEXT(value):
197 """Decode RFC-2047 TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
198 from email.Header import decode_header
199 atoms = decode_header(value)
200 decodedvalue = ""
201 for atom, charset in atoms:
202 if charset is not None:
203 atom = atom.decode(charset)
204 decodedvalue += atom
205 return decodedvalue
206
208 """Return legal HTTP status Code, Reason-phrase and Message.
209
210 The status arg must be an int, or a str that begins with an int.
211
212 If status is an int, or a str and no reason-phrase is supplied,
213 a default reason-phrase will be provided.
214 """
215
216 if not status:
217 status = 200
218
219 status = str(status)
220 parts = status.split(" ", 1)
221 if len(parts) == 1:
222
223 code, = parts
224 reason = None
225 else:
226 code, reason = parts
227 reason = reason.strip()
228
229 try:
230 code = int(code)
231 except ValueError:
232 raise ValueError("Illegal response status from server "
233 "(%s is non-numeric)." % repr(code))
234
235 if code < 100 or code > 599:
236 raise ValueError("Illegal response status from server "
237 "(%s is out of range)." % repr(code))
238
239 if code not in response_codes:
240
241 default_reason, message = "", ""
242 else:
243 default_reason, message = response_codes[code]
244
245 if reason is None:
246 reason = default_reason
247
248 return code, reason, message
249
250
251 image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
252
254 """Build a params dictionary from a query_string.
255
256 Duplicate key/value pairs in the provided query_string will be
257 returned as {'key': [val1, val2, ...]}. Single key/values will
258 be returned as strings: {'key': 'value'}.
259 """
260 if image_map_pattern.match(query_string):
261
262
263 pm = query_string.split(",")
264 pm = {'x': int(pm[0]), 'y': int(pm[1])}
265 else:
266 pm = cgi.parse_qs(query_string, keep_blank_values)
267 for key, val in pm.items():
268 if len(val) == 1:
269 pm[key] = val[0]
270 return pm
271
291
292
294 """A case-insensitive dict subclass.
295
296 Each key is changed on entry to str(key).title().
297 """
298
301
304
307
310
311 - def get(self, key, default=None):
313
316
320
322 newdict = cls()
323 for k in seq:
324 newdict[str(k).title()] = value
325 return newdict
326 fromkeys = classmethod(fromkeys)
327
329 key = str(key).title()
330 try:
331 return self[key]
332 except KeyError:
333 self[key] = x
334 return x
335
336 - def pop(self, key, default):
338
339
341 """A dict subclass for HTTP request and response headers.
342
343 Each key is changed on entry to str(key).title(). This allows headers
344 to be case-insensitive and avoid duplicates.
345
346 Values are header values (decoded according to RFC 2047 if necessary).
347 """
348
350 """Return a list of HeaderElements for the given header (or None)."""
351 key = str(key).title()
352 h = self.get(key)
353 if h is None:
354 return []
355 return header_elements(key, h)
356
358 """Transform self into a list of (name, value) tuples."""
359 header_list = []
360 for key, v in self.iteritems():
361 if isinstance(v, unicode):
362
363
364
365
366
367 try:
368 v = v.encode("iso-8859-1")
369 except UnicodeEncodeError:
370 if protocol >= (1, 1):
371
372
373 from email.Header import Header
374 v = Header(v, 'utf-8').encode()
375 else:
376 raise
377 else:
378
379
380 v = str(v)
381 header_list.append((key, v))
382 return header_list
383
384
385
387 """An internet address.
388
389 name should be the client's host name. If not available (because no DNS
390 lookup is performed), the IP address should be used instead.
391 """
392
393 ip = "0.0.0.0"
394 port = 80
395 name = "unknown.tld"
396
397 - def __init__(self, ip, port, name=None):
403
405 return "http.Host(%r, %r, %r)" % (self.ip, self.port, self.name)
406