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   
376   
377   
379 -    def __init__(self, status=500, message=None): 
 386   
388          return getattr(self._response, name) 
 389   
391          return unicode(self._response.body) 
  392   
394      """Raise this exception if a requested resource is not found. 
395   
396      """ 
 400   
402      """Raise this exception if a method which is not allowed was used. 
403   
404      """ 
405 -    def __init__(self, request_method, allowed_methods): 
 406          if method_known(request_method): 
407              HTTPError.__init__(self, 405) 
408               
409              allow = list(allowed_methods) 
410              allow.sort() 
411              self.headers.set(u'Allow', u', '.join(allow)) 
412          else: 
413              raise HTTPError(501, "Request method %s is not implemented" % 
414                              request_method) 
  415   
417      """Raise this exception if a server error occurs. 
418   
419      """ 
 422   
423   
424