1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 r"""Support utilities for building a WSGI application.
21
22 """
23 __docformat__ = "restructuredtext en"
24
25 import cgi
26 import re
27 import StringIO
28 import pathinfo
29 import urllib
30
31 import reason_phrases
32
34 """Convert text into unicode, if it's not already unicode.
35
36 """
37 if isinstance(text, str):
38 return text.decode('utf-8')
39 return text
40
42 """Return True iff the method string is one of the known HTTP methods.
43
44 """
45 return method in (
46 'OPTIONS',
47 'GET',
48 'HEAD',
49 'POST',
50 'PUT',
51 'DELETE',
52 'TRACE',
53 'CONNECT',
54 )
55
57 """Request object, used to represent a request via WSGI.
58
59 """
61 self.path = to_uni(environ.get('PATH_INFO', u'/'))
62 if not self.path:
63 self.path = u'/'
64 self.path_components = self.path.split(u'/')[1:]
65
66
67 self.method = environ['REQUEST_METHOD'].upper()
68
69 self.content_length = 0
70 self.input = None
71 if self.method in ('POST', 'PUT'):
72
73 self.content_length = int(environ.get('CONTENT_LENGTH', 0))
74 if self.content_length > 0:
75 self.input = environ['wsgi.input']
76 self.content_type = environ.get('CONTENT_TYPE', '')
77
78 self.GET = cgi.parse_qs(environ.get('QUERY_STRING', ''))
79 self.params = self.GET
80
82 """Set the WSGIWAPI properties found on the handler.
83
84 This is used to warn when the properties on the handler do not match
85 those used by the decorator - this is usually due to a second decorator
86 dropping the properties.
87
88 """
89 self._handler_props = handler_props
90
96
98 return u"Request(%s, \"%s\", %r)" % (
99 self.method,
100 self.path,
101 self.GET
102 )
103
105 """Object satisfying the WSGI protocol for making a response.
106
107 This object should be passed the start_reponse parameter (as supplied by
108 the WSGI protocol), and the Response object for the response. The status
109 code, headers, and response body will be read from the Response object.
110
111 """
112 - def __init__(self, start_response, response):
115
120
122 return len(self.response.body)
123
125 """Convert a string to a byte string encoded in us-ascii.
126
127 If the input is a byte string, this simply checks that all characters in it
128 are us-ascii.
129
130 - `value` is the value to convert.
131 - `description` is a description of the string, used in error messages.
132
133 """
134 if isinstance(value, unicode):
135 try:
136 value = value.encode('us-ascii')
137 except UnicodeError, e:
138 e.reason += ", %s must be encodable as US-ASCII" % description
139 raise
140 else:
141 try:
142 value.decode('us-ascii')
143 except UnicodeError, e:
144 e.reason += ", %s must be encodable as US-ASCII" % description
145 raise
146 return value
147
148
149 _tspecials_pattern = re.compile(r'[\(\)<>@,;:\\"/\[\]\?=]')
150
151
153 """Check that a token only contains characters which are in us-ascii range
154 33-127 inclusive, and are not in tspecials.
155
156 """
157 for char in token:
158 if ord(char) < 33 or ord(char) > 127:
159 return False
160 if _tspecials_pattern.match(char):
161 return False
162 return True
163
165
166
167 paramvalue = paramvalue.encode('utf-8')
168
169
170
171 paramvalue = urllib.quote(paramvalue, safe='-._~:!$&+')
172
173 return paramname + "*", "utf-8''" + paramvalue
174
178
180 """Set the value of a header.
181
182 If any values for the header already exist in the list of headers, they
183 are first removed. The comparison of header names is performed case
184 insensitively.
185
186 """
187 self.remove(header)
188 self.add(header, value, params, **kwargs)
189
191 """Add a header value.
192
193 FIXME - document
194
195 """
196 header = _string_to_ascii(header, "header name")
197 value = _string_to_ascii(value, "header value")
198
199
200 formatted_params = []
201 def iteritems(a, b):
202 for k, v in a.iteritems():
203 yield k, v
204 for k, v in b.iteritems():
205 yield k, v
206 for paramname, paramvalue in iteritems(params, kwargs):
207 paramname = _string_to_ascii(paramname, "parameter name")
208
209
210 if not _validate_token(paramname):
211 raise InvalidArgumentError("Parameter name contained "
212 "invalid characters")
213
214
215
216
217 try:
218 paramvalue = _string_to_ascii(paramvalue, "parameter value")
219 except UnicodeError, e:
220 if not isinstance(paramvalue, unicode):
221 raise
222
223
224 paramname, paramvalue = _encode_with_rfc2231(paramname, paramvalue)
225 else:
226 if not _validate_token(paramvalue):
227
228 paramvalue = '"' + paramvalue.replace('\\', '\\\\').\
229 replace('"', '\\"') + '"'
230 formatted_params.append("%s=%s" % (paramname, paramvalue))
231 if len(formatted_params):
232 value = value + '; ' + '; '.join(formatted_params)
233 self._headers.append((header, value))
234
236 """Get the first value of a named header.
237
238 Returns `default` if no values of the named header exist.
239
240 """
241 header = _string_to_ascii(header, "header name").lower()
242 for key, value in self._headers:
243 if key.lower() == header:
244 return value
245 return default
246
248 """Get all values of a named header.
249
250 Returns the values in the order in which they were added.
251
252 Returns an empty list if no values of the named header are present.
253
254 """
255 header = _string_to_ascii(header, "header name").lower()
256 return [value for (key, value) in self._headers if key.lower() == header]
257
259 """Remove any occurrences of the named header.
260
261 The comparison of header names is performed case insensitively.
262
263 """
264 self._headers = filter(lambda x: x[0].lower() != header.lower(), self._headers)
265
267 """Get the list of headers.
268
269 This returns a list of tuple pairs, ``(header, value)``, one for each
270 header, in the order added. The strings in the tuples are byte
271 strings, encoded appropriately for HTTP transmission.
272
273 """
274 return self._headers
275
277 """Get a string representation of the headers.
278
279 """
280 return str(self.items())
281
283 """Response object, used to return stuff via WSGI protocol.
284
285 The Response object is a container fr the details of the response. It
286 contains three significant members:
287
288 - status: The status code (as a string, with code and reason phrase) for
289 the reponse.
290 - headers: The headers to return about the request.
291 - body: The body of the page to return.
292
293 """
294 - def __init__(self, body=u'', status=200, content_type=u'text/plain'):
295 """Create a new Response object.
296
297 The body defaults to being empty, the status defaults to "200 OK", and
298 the content_type defaults to 'text/plain'.
299
300 - body: The value to store in the body member. Defaults to ''.
301 - status: The status to set for the response. May be specified as a
302 number, or as a string (optionally, with a reason phrase). Defaults
303 to 200.
304 - content_type: The content type to set for the response (as specified
305 for the set_content_type() method). Defaults to 'text/plain'.
306
307 """
308 self.body = body
309 self.status = status
310 self.headers = Headers()
311 self.set_content_type(content_type)
312
313 VALID_STATUS_RE = re.compile(r'[12345][0-9][0-9]')
315 if isinstance(status, basestring):
316 if len(status) == 3:
317 pass
318 elif len(status) <= 4:
319 raise ValueError(u"Supplied status (%r) is not valid" % status)
320 elif status[3] == ' ':
321 if not Response.VALID_STATUS_RE.match(status[:3]):
322 raise ValueError(u"Supplied status (%r) is not valid" %
323 status)
324 self._status = _string_to_ascii(status, 'HTTP status line')
325 return
326
327 try:
328 statusint = int(status)
329 except ValueError:
330 raise ValueError(u"Supplied status (%r) is not a valid "
331 "status code" % status)
332
333 if statusint < 100 or statusint >= 600:
334 raise ValueError(u"Supplied status (%r) is not in valid range" %
335 status)
336
337 try:
338 self._status = reason_phrases.phrase_dict[statusint]
339 except KeyError:
340 raise ValueError(u"Supplied status (%r) is not known" %
341 status)
342
345 status = property(_get_status, _set_status, doc=
346 """The status line to return.
347
348 This may be set to either a string or a number. If a string, it may
349 either contain only the status code, or may contain a reason phrase
350 following the status code (separated by a space).
351
352 If there is no reason phrase, or the status code is a number, an
353 appropriate reason phrase will be used, as long as the status code is
354 one of the standard HTTP 1.1 codes. For non-standard codes, the reason
355 phrase must be supplied.
356
357 If `status` is a unicode string, it must contain only characters which
358 can be encoded in the US-ASCII character set. Any other characters
359 will cause an exception to be thrown.
360
361 """)
362
363 - def set_content_type(self, content_type):
364 """Set the content type to return.
365
366 """
367 self.headers.set(u'Content-Type', content_type)
368
370 return "Response(%s, %s, %s)" % (
371 self.status,
372 self.headers,
373 self.body
374 )
375
377 - def __init__(self, status=500, message=None):
384
386 """Raise this exception if a requested resource is not found.
387
388 """
392
394 """Raise this exception if a method which is not allowed was used.
395
396 """
397 - def __init__(self, request_method, allowed_methods):
398 if method_known(request_method):
399 HTTPError.__init__(self, 405)
400
401 allow = list(allowed_methods)
402 allow.sort()
403 self.headers.set(u'Allow', u', '.join(allow))
404 else:
405 raise HTTPError(501, "Request method %s is not implemented" %
406 request_method)
407
409 """Raise this exception if a server error occurs.
410
411 """
414
415
416