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 from email.Header import Header, decode_header
24 import re
25 import rfc822
26 HTTPDate = rfc822.formatdate
27 import time
28
29
31 url = "/".join(atoms)
32 while "//" in url:
33 url = url.replace("//", "/")
34 return url
35
37 """Return a protocol tuple from the given 'HTTP/x.y' string."""
38 return int(protocol_str[5]), int(protocol_str[7])
39
41 """Return a list of (start, stop) indices from a Range header, or None.
42
43 Each (start, stop) tuple will be composed of two ints, which are suitable
44 for use in a slicing operation. That is, the header "Range: bytes=3-6",
45 if applied against a Python string, is requesting resource[3:7]. This
46 function will return the list [(3, 7)].
47
48 If this function return an empty list, you should return HTTP 416.
49 """
50
51 if not headervalue:
52 return None
53
54 result = []
55 bytesunit, byteranges = headervalue.split("=", 1)
56 for brange in byteranges.split(","):
57 start, stop = [x.strip() for x in brange.split("-", 1)]
58 if start:
59 if not stop:
60 stop = content_length - 1
61 start, stop = map(int, (start, stop))
62 if start >= content_length:
63
64
65
66
67
68
69
70
71 continue
72 if stop < start:
73
74
75
76
77
78
79 return None
80 result.append((start, stop + 1))
81 else:
82 if not stop:
83
84 return None
85
86 result.append((content_length - int(stop), content_length))
87
88 return result
89
90
92 """An element (with parameters) from an HTTP header's element list."""
93
99
101 p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()]
102 return u"%s%s" % (self.value, "".join(p))
103
106
108 """Transform 'token;key=val' to ('token', {'key': 'val'})."""
109
110
111 atoms = [x.strip() for x in elementstr.split(";")]
112 initial_value = atoms.pop(0).strip()
113 params = {}
114 for atom in atoms:
115 atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
116 key = atom.pop(0)
117 if atom:
118 val = atom[0]
119 else:
120 val = ""
121 params[key] = val
122 return initial_value, params
123 parse = staticmethod(parse)
124
126 """Construct an instance from a string of the form 'token;key=val'."""
127 ival, params = cls.parse(elementstr)
128 return cls(ival, params)
129 from_str = classmethod(from_str)
130
131
132 q_separator = re.compile(r'; *q *=')
133
135 """An element (with parameters) from an Accept-* header's element list."""
136
152 from_str = classmethod(from_str)
153
155 val = self.params.get("q", "1")
156 if isinstance(val, HeaderElement):
157 val = val.value
158 return float(val)
159 qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
160
162
163
164 diff = cmp(other.qvalue, self.qvalue)
165 if diff == 0:
166 diff = cmp(str(other), str(self))
167 return diff
168
169
171 """Return a HeaderElement list from a comma-separated header str."""
172
173 if not fieldvalue:
174 return None
175 headername = fieldname.lower()
176
177 result = []
178 for element in fieldvalue.split(","):
179 if headername.startswith("accept") or headername == 'te':
180 hv = AcceptElement.from_str(element)
181 else:
182 hv = HeaderElement.from_str(element)
183 result.append(hv)
184
185 result.sort()
186 return result
187
188 -def decode_TEXT(value):
189 """Decode RFC-2047 TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
190 atoms = decode_header(value)
191 decodedvalue = ""
192 for atom, charset in atoms:
193 if charset is not None:
194 atom = atom.decode(charset)
195 decodedvalue += atom
196 return decodedvalue
197
199 """Return legal HTTP status Code, Reason-phrase and Message.
200
201 The status arg must be an int, or a str that begins with an int.
202
203 If status is an int, or a str and no reason-phrase is supplied,
204 a default reason-phrase will be provided.
205 """
206
207 if not status:
208 status = 200
209
210 status = str(status)
211 parts = status.split(" ", 1)
212 if len(parts) == 1:
213
214 code, = parts
215 reason = None
216 else:
217 code, reason = parts
218 reason = reason.strip()
219
220 try:
221 code = int(code)
222 except ValueError:
223 raise ValueError("Illegal response status from server "
224 "(%s is non-numeric)." % repr(code))
225
226 if code < 100 or code > 599:
227 raise ValueError("Illegal response status from server "
228 "(%s is out of range)." % repr(code))
229
230 if code not in response_codes:
231
232 default_reason, message = "", ""
233 else:
234 default_reason, message = response_codes[code]
235
236 if reason is None:
237 reason = default_reason
238
239 return code, reason, message
240
241
242 image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
243
245 """Build a params dictionary from a query_string."""
246 if image_map_pattern.match(query_string):
247
248
249 pm = query_string.split(",")
250 pm = {'x': int(pm[0]), 'y': int(pm[1])}
251 else:
252 pm = cgi.parse_qs(query_string, keep_blank_values)
253 for key, val in pm.items():
254 if len(val) == 1:
255 pm[key] = val[0]
256 return pm
257
277
278
280 """A case-insensitive dict subclass.
281
282 Each key is changed on entry to str(key).title().
283 """
284
287
290
293
296
297 - def get(self, key, default=None):
299
302
306
308 newdict = cls()
309 for k in seq:
310 newdict[str(k).title()] = value
311 return newdict
312 fromkeys = classmethod(fromkeys)
313
315 key = str(key).title()
316 try:
317 return self[key]
318 except KeyError:
319 self[key] = x
320 return x
321
322 - def pop(self, key, default):
324
325
327 """A dict subclass for HTTP request and response headers.
328
329 Each key is changed on entry to str(key).title(). This allows headers
330 to be case-insensitive and avoid duplicates.
331
332 Values are header values (decoded according to RFC 2047 if necessary).
333 """
334
336 """Return a list of HeaderElements for the given header (or None)."""
337 key = str(key).title()
338 h = self.get(key)
339 if h is None:
340 return []
341 return header_elements(key, h)
342
344 """Transform self into a list of (name, value) tuples."""
345 header_list = []
346 for key, v in self.iteritems():
347 if isinstance(v, unicode):
348
349
350
351
352
353 try:
354 v = v.encode("iso-8859-1")
355 except UnicodeEncodeError:
356 if protocol >= (1, 1):
357
358
359 v = Header(v, 'utf-8').encode()
360 else:
361 raise
362 else:
363
364
365 v = str(v)
366 header_list.append((key, v))
367 return header_list
368
369
372
374 """Wraps a file-like object, raising MaxSizeExceeded if too large."""
375
377 self.rfile = rfile
378 self.maxlen = maxlen
379 self.bytes_read = 0
380
382 if self.maxlen and self.bytes_read > self.maxlen:
383 raise MaxSizeExceeded()
384
385 - def read(self, size = None):
390
409
411
412 total = 0
413 lines = []
414 line = self.readline()
415 while line:
416 lines.append(line)
417 total += len(line)
418 if 0 < sizehint <= total:
419 break
420 line = self.readline()
421 return lines
422
425
428
434
435
437 """An internet address.
438
439 name should be the client's host name. If not available (because no DNS
440 lookup is performed), the IP address should be used instead.
441 """
442
443 ip = "0.0.0.0"
444 port = 80
445 name = "unknown.tld"
446
447 - def __init__(self, ip, port, name=None):
453
455 return "http.Host(%r, %r, %r)" % (self.ip, self.port, self.name)
456