1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Response codes
19 ==============
20
21 This module stores HTTP response codes.
22
23 :Variables:
24 - `classes`: Mapping status code -> HTTPResponse
25 (``{status: HTTPResponse, ...}``)
26 - `reasons`: Mapping status code -> reason phrase
27 (``{status: 'reason', ...}``)
28
29 :Types:
30 - `classes`: ``dict``
31 - `reasons`: ``dict``
32 """
33 __author__ = u"Andr\xe9 Malo"
34 __docformat__ = "restructuredtext en"
35
36 from wtf import webutil as _webutil
37
38
40 """
41 Base HTTP error response exception class
42
43 The exception is derived from `SystemExit` on purpose - that way it
44 should wind up the whole try-except stack (if well-written: nobody should
45 swallow `SystemExit`) until explicitly caught.
46
47 :CVariables:
48 - `_FRAME`: Frame around the actual message
49 - `status`: HTTP response status
50 - `reason`: HTTP response reason phrase
51
52 :IVariables:
53 - `message`: Message template
54 - `param`: Additional parameters for response template fill-in
55 - `_escaped`: Already escaped parameters
56 - `_content_type`: Content-Type
57 - `_replace`: Replace parameters in message?
58
59 :Types:
60 - `_FRAME`: ``str``
61 - `status`: ``int``
62 - `reason`: ``str``
63 - `message`: ``str``
64 - `param`: ``dict``
65 - `_escaped`: ``dict``
66 - `_content_type`: ``str``
67 - `_replace`: ``bool``
68 """
69 status, reason, message = None, None, None
70 _FRAME = """
71 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
72 <html><head>
73 <title>%%(status)s %%(reason)s</title>
74 </head><body>
75 <h1>%%(reason)s</h1>
76 %s
77 </body></html>
78 """.strip()
79
80 - def __init__(self, request, message=None,
81 content_type='text/html; charset=us-ascii', replace=True,
82 **param):
83 """
84 Initialization
85
86 :Parameters:
87 - `request`: Request object
88 - `message`: message template
89 - `content_type`: Response content type
90 - `replace`: Replace parameters in message?
91 - `param`: Additional parameters for response template fill-in
92
93 :Types:
94 - `request`: `wtf.app.request.Request`
95 - `message`: ``str``
96 - `content_type`: ``str``
97 - `replace`: ``bool``
98 - `param`: ``dict``
99 """
100 SystemExit.__init__(self, 0)
101 if message is not None:
102 self.message = message
103 elif self.message is not None:
104 self.message = self._FRAME % self.message
105 self._content_type = content_type
106 self._replace = bool(replace)
107 self._request = request
108 self.param, self._escaped = self.init(**param)
109 url = request.url.copy()
110 url.scheme, url.netloc = u'', u''
111 self.param.setdefault('url', str(url))
112 self.param.setdefault('method', str(request.method))
113
115 """
116 Custom initializer and easy check for required parameters
117
118 :return: tuple of unescaped and escaped parameters (``(dict, dict)``)
119 :rtype: ``tuple``
120 """
121 return {}, {}
122
124 """ Modify response headers """
125 collection.set('content-type', self._content_type)
126
128 """
129 Compute the response body
130
131 :return: The response body
132 :rtype: ``str``
133 """
134 if self.message is None:
135 return ""
136 elif not self._replace:
137 return self.message
138 param = dict(self.param)
139 param.update({'status': str(self.status), 'reason': str(self.reason)})
140 param = dict((key, _webutil.escape_html(str(val)))
141 for key, val in param.iteritems())
142 param.update(self._escaped)
143 return self.message % param
144
145
149
150
152 """ 101 Switching Protocols (RFC 2616) """
153 status, reason, message = 101, "Switching Protocols", None
154
155
159
160
161 -class OK(HTTPResponse):
164
165
166
167
169 """ 201 Created (RFC 2616) """
170 status, reason = 201, "Created"
171 message = """
172 <p>A new resource has been created and is available under the following
173 URI(s):</p>
174 <ul>
175 <li><a href="%(location)s">%(location)s</a></li>
176 %(uris)s
177 </ul>
178 """.strip()
179
180 - def init(self, location, additional=()):
181 """
182 Add main location and (optional) less significant URIs
183
184 :Parameters:
185 - `location`: Main location of the created resource
186 - `additional`: List of additional (less significant) locations
187 (``('uri', ...)``)
188
189 :Types:
190 - `location`: ``str``
191 - `additional`: ``iterable``
192
193 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
194 :rtype: ``tuple``
195 """
196 urilist = "\n".join("<li><a href=\"%(uri)s\">%(uri)s</a></li>" %
197 _webutil.escape_html(uri) for uri in additional)
198 return dict(location=location, uris=additional), dict(uris=urilist)
199
204
205
207 """ 202 Accepted (RFC 2616) """
208 status, reason = 202, "Accepted"
209 message = """
210 <p>Your request has been accepted and may be processed later.</p>
211 """.strip()
212
213
217
218
219 -class NoContent(HTTPResponse):
220 """ 204 No Content (RFC 2616) """
221 status, reason, message = 204, "No Content", None
222
223
224 -class ResetContent(HTTPResponse):
225 """ 205 Reset Content (RFC 2616) """
226 status, reason, message = 205, "Reset Content", None
227
228
229 -class PartialContent(HTTPResponse):
230 """ 206 Partial Content (RFC 2616) """
231 status, reason, message = 206, "Partial Content", None
232
233
235 """ 207 Multi-Status (RFC 2518) """
236 status, reason, message = 207, "Multi-Status", None
237
238
240 """ 300 Multiple Choices (RFC 2616) """
241 status, reason = 300, "Multiple Choices"
242 message = """
243 <p>Multiple representations available:</p>
244 <ul>
245 %(variants)s
246 </ul>
247 """.strip()
248
249 - def init(self, variants):
250 """
251 Add list of variants
252
253 :Parameters:
254 - `variants`: List of choosable variants (``('uri', ...)``)
255
256 :Types:
257 - `variants`: ``iterable``
258
259 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
260 :rtype: ``tuple``
261 """
262 variantlist = "\n".join("<li>%(variant)s</li>" %
263 _webutil.escape_html(variant) for variant in variants)
264 return dict(variants=variants), dict(variants=variantlist)
265
266
268 """ Base redirect class """
269 message = """
270 <p>The document has moved <a href="%(location)s">here</a>.</p>
271 """.strip()
272
273 - def init(self, location):
274 """
275 Ensure location parameter
276
277 :Parameters:
278 - `location`: New location
279
280 :Types:
281 - `location`: ``str``
282
283 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
284 :rtype: ``tuple``
285 """
286 return dict(location=location), {}
287
292
293
295 """ Subclass for type identification of automatic redirects """
296
297
299 """ 301 Moved Permanently (RFC 2616) """
300 status, reason = 301, "Moved Permanently"
301
302
303 -class Found(HTTPRedirectResponse):
304 """ 302 Found (RFC 2616) """
305 status, reason = 302, "Found"
306
307
309 """ 303 See Other (RFC 2616) """
310 status, reason = 303, "See Other"
311 message = """
312 <p>The answer to your request is located <a href="%(location)s">here</a>.</p>
313 """.strip()
314
315
317 """ 304 Not Modified (RFC 2616) """
318 status, reason, message = 304, "Not Modified", None
319
320
322 """ 305 Use Proxy (RFC 2616) """
323 status, reason = 305, "Use Proxy"
324 message = """
325 <p>This resource is only accessible through the proxy
326 %(location)s<br>
327 You will need to configure your client to use that proxy.</p>
328 """.strip()
329
330
332 """ 306 (Unused) (RFC 2616) """
333 status, reason, message = 306, "Unused", "<p>Unused Status Code</p>"
334
335
337 """ 307 Temporary Redirect (RFC 2616) """
338 status, reason = 307, "Temporary Redirect"
339
340
342 """ 400 Bad Request (RFC 2616) """
343 status, reason = 400, "Bad Request"
344 message = """
345 <p>Your browser sent a request that this server could not understand.<br>
346 %(hint)s</p>
347 """.strip()
348
349 - def init(self, hint=None):
350 """
351 Add an error hint
352
353 :Parameters:
354 - `hint`: Optional hint describing the error (unescaped HTML)
355
356 :Types:
357 - `hint`: ``str``
358
359 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
360 :rtype: ``tuple``
361 """
362 hint_esc = hint and _webutil.escape_html(hint) or ""
363 return dict(hint=hint), dict(hint=hint_esc)
364
365
367 """ 401 Authorization Required (RFC 2616) """
368 status, reason = 401, "Authorization Required"
369 message = """
370 <p>This server could not verify that you
371 are authorized to access the document
372 requested. Either you supplied the wrong
373 credentials (e.g., bad password), or your
374 browser doesn't understand how to supply
375 the credentials required.</p>
376 """.strip()
377
378 - def init(self, auth_type, realm):
379 """
380 Add auth type and realm
381
382 :Parameters:
383 - `auth_type`: Authentication Type (like 'Basic', see RFC 2617)
384 - `realm`: Authentication realm
385
386 :Types:
387 - `auth_type`: ``str``
388 - `realm`: ``str``
389
390 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
391 :rtype: ``tuple``
392 """
393 assert auth_type.lower() == 'basic', \
394 'Only basic authentication supported yet'
395 return dict(auth_type=auth_type, realm=realm), {}
396
398 """ Modify response headers """
399 HTTPResponse.headers(self, collection)
400 collection.set('WWW-Authenticate', '%s realm="%s"' % (
401 self.param['auth_type'], self.param['realm'].replace('"', '\\"')
402 ))
403
404
406 """ 402 Payment Required (RFC 2616) """
407 status, reason = 402, "Payment Required"
408 message = """
409 <p>This resource requires payment.</p>
410 """.strip()
411
412
414 """ 403 Forbidden (RFC 2616) """
415 status, reason = 403, "Forbidden"
416 message = """
417 <p>You don't have permission to access %(url)s
418 non this server.</p>
419 """.strip()
420
421
423 """ 404 Not Found (RFC 2616) """
424 status, reason = 404, "Not Found"
425 message = """
426 <p>The requested URL %(url)s was not found on this server.</p>
427 """.strip()
428
429
431 """ 405 Method Not Allowed """
432 status, reason = 405, "Method Not Allowed"
433 message = """
434 <p>The requested method %(method)s is not allowed for the URL %(url)s.</p>
435 """.strip()
436
437 - def init(self, allowed):
438 """
439 Add the allowed method list
440
441 :Parameters:
442 - `allowed`: List of allowed methods (``('method', ...)``)
443
444 :Types:
445 - `allowed`: ``iterable``
446
447 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
448 :rtype: ``tuple``
449 """
450 allowed = list(sorted(set(allowed)))
451 allowed_esc = '\n'.join("<li>%s</li>" % _webutil.escape_html(method)
452 for method in allowed)
453 return dict(allowed=allowed), dict(allowed=allowed_esc)
454
456 """ Modify response headers """
457 HTTPResponse.headers(self, collection)
458 collection.set('allow', ', '.join(self.param['allowed']))
459
460
462 """ 406 Not Acceptable (RFC 2616) """
463 status, reason = 406, "Not Acceptable"
464 message = """
465 <p>An appropriate representation of the requested resource %(url)s
466 could not be found on this server.</p>
467 Available variants:
468 <ul>
469 %(variants)s
470 </ul>
471 """.strip()
472
473 - def init(self, variants, descriptions=None):
474 """
475 Add the variant list
476
477 :Parameters:
478 - `variants`: The variant URLs
479 - `descriptions`: variant descriptions, the url is the key
480
481 :Types:
482 - `variants`: ``iterable``
483 - `descriptions`: ``dict``
484
485 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
486 :rtype: ``tuple``
487 """
488 if descriptions is None:
489 descriptions = {}
490 variants_esc = '\n'.join(
491 '<li><a href="%(var)s">%(var)s</a>%(desc)s</li>' % dict(
492 var=_webutil.escape_html(var),
493 desc=descriptions.get(var) and
494 (", " + _webutil.escape_html(descriptions[var])) or "",
495 ) for var in variants)
496 return (
497 dict(variants=variants, descriptions=descriptions),
498 dict(variants=variants_esc, descriptions="")
499 )
500
501
503 """ 407 Proxy Authentication Required (RFC 2616) """
504 status, reason = 407, "Proxy Authentication Required"
505
506
508 """ 408 Request Timeout (RFC 2616) """
509 status, reason = 408, "Request Timeout"
510 message = """
511 <p>Server timeout waiting for the HTTP request from the client.</p>
512 """.strip()
513
514
516 """ 409 Conflict (RFC 2616) """
517 status, reason = 409, "Conflict"
518 message = """
519 <p>The request could not be completed due to a conflict with the current
520 state of the resource.</p>%(desc)s
521 """.strip()
522
523 - def init(self, desc=None):
524 """
525 Optionally add a conflict description
526
527 :Parameters:
528 - `desc`: Optional conflict description
529
530 :Types:
531 - `desc`: ``str``
532
533 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
534 :rtype: ``tuple``
535 """
536 desc_esc = desc and "\n<p>%s</p>" % _webutil.escape_html(desc) or ""
537 return dict(desc=desc), dict(desc=desc_esc)
538
539
540 -class Gone(HTTPResponse):
541 """ 410 Gone (RFC 2616) """
542 status, reason = 410, "Gone"
543 message = """
544 <p>The requested resource<br>%(url)s<br>
545 is no longer available on this server and there is no forwarding address.
546 Please remove all references to this resource.</p>
547 """.strip()
548
549
551 """ 411 Length Required (RFC 2616) """
552 status, reason = 411, "Length Required"
553 message = """
554 <p>A request of the requested method %(method)s requires a valid
555 Content-length.%(desc)s</p>
556 """.strip()
557
558 - def init(self, desc=None):
559 """
560 Add optional description
561
562 :Parameters:
563 - `desc`: Optional additional description
564
565 :Types:
566 - `desc`: ``str``
567
568 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
569 :rtype: ``tuple``
570 """
571 desc_esc = desc and ("<br>\n" + _webutil.escape_html(desc)) or ""
572 return dict(desc=desc), dict(desc=desc_esc)
573
574
576 """ 412 Precondition Failed (RFC 2616) """
577 status, reason = 412, "Precondition Failed"
578 message = """
579 <p>The precondition on the request for the URL %(url)s evaluated to false.</p>
580 """.strip()
581
582
591
592
616
617
625
626
628 """ 416 Request Range Not Satisfiable (RFC 2616) """
629 status, reason = 416, "Request Range Not Satisfiable"
630 message = """
631 <p>None of the range-specifier values in the Range
632 request-header field overlap the current extent
633 of the selected resource.</p>
634 """.strip()
635
636
638 """ 417 Expectation Failed (RFC 2616) """
639 status, reason = 417, "Expectation Failed"
640 message = """
641 <p>The expectation given in an Expect request-header field could not be met
642 by this server.</p>
643 """.strip()
644
645
647 """ 422 Unprocessable Entity (RFC 2518) """
648 status, reason = 422, "Unprocessable Entity"
649 message = """
650 <p>The server understands the media type of the
651 request entity, but was unable to process the
652 contained instructions.</p>
653 """.strip()
654
655
657 """ 423 Locked (RFC 2518) """
658 status, reason = 423, "Locked"
659 message = """
660 <p>The requested resource is currently locked.
661 The lock must be released or proper identification
662 given before the method can be applied.</p>
663 """.strip()
664
665
667 """ 424 Failed Dependency (RFC 2518) """
668 status, reason = 424, "Failed Dependency"
669 message = """
670 <p>The method could not be performed on the resource
671 because the requested action depended on another
672 action and that other action failed.</p>
673 """.strip()
674
675
677 """ 426 Upgrade Required (RFC 2817) """
678 status, reason = 426, "Upgrade Required"
679 message = """
680 <p>The requested resource can only be retrieved
681 using SSL. The server is willing to upgrade the current
682 connection to SSL, but your client doesn't support it.
683 Either upgrade your client, or try requesting the page
684 using https://.</p>
685 """.strip()
686
687 - def init(self, tokens):
688 """
689 Add upgrade tokens
690
691 :Parameters:
692 - `tokens`: List of upgrade tokens to advertise
693
694 :Types:
695 - `tokens`: ``iterable``
696
697 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
698 :rtype: ``tuple``
699 """
700 tokens_esc = _webutil.escape_html(", ".join(tokens))
701 return dict(tokens=tokens), dict(tokens=tokens_esc)
702
704 """ Modify response headers """
705 HTTPResponse.headers(self, collection)
706 collection.add('connection', 'upgrade')
707 collection.set('upgrade', ', '.join(self.param['tokens']))
708
709
711 """ 500 Internal Server Error (RFC 2616) """
712 status, reason = 500, "Internal Server Error"
713 message = """
714 <p>The server encountered an internal error or
715 misconfiguration and was unable to complete
716 your request.</p>
717 <p>Please contact the server administrator,%(admin)s and inform them of the
718 time the error occurred,
719 and anything you might have done that may have
720 caused the error.</p>
721 <p>More information about this error may be available
722 in the server error log.</p>
723 """.strip()
724
725 - def init(self, admin=None):
726 """
727 Add optional admin address
728
729 :Parameters:
730 - `admin`: The optional admin contact address
731
732 :Types:
733 - `admin`: ``str``
734
735 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
736 :rtype: ``tuple``
737 """
738 admin_esc = admin and (" " + _webutil.escape_html(admin)) or ""
739 return dict(admin=admin), dict(admin=admin_esc)
740
741
743 """ 501 Not Implemented (RFC 2616) """
744 status, reason = 501, "Not Implemented"
745 message = """
746 <p>%(method)s to %(url)s not supported.<br>
747 %(desc)s</p>
748 """.strip()
749
750 - def init(self, desc=None):
751 """
752 Add optional description
753
754 :Parameters:
755 - `desc`: Optional error description
756
757 :Types:
758 - `desc`: ``str``
759
760 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
761 :rtype: ``tuple``
762 """
763 desc_esc = desc and _webutil.escape_html(desc) or ""
764 return dict(desc=desc), dict(desc=desc_esc)
765
766
768 """ 502 Bad Gateway (RFC 2616) """
769 status, reason = 502, "Bad Gateway"
770 message = """
771 <p>The proxy server received an invalid
772 response from an upstream server.<br>%(desc)s</p>
773 """.strip()
774
775 - def init(self, desc=None):
776 """
777 Optionally add a error description
778
779 :Parameters:
780 - `desc`: Optional error description
781
782 :Types:
783 - `desc`: ``str``
784
785 :return: Tuple of unescaped and escaped parameters (``(dict, dict)``)
786 :rtype: ``tuple``
787 """
788 desc_esc = desc and _webutil.escape_html(desc) or ""
789 return dict(desc=desc), dict(desc=desc_esc)
790
791
793 """ 503 Service Unavailable (RFC 2616) """
794 status, reason = 503, "Service Unavailable"
795 message = """
796 <p>The server is temporarily unable to service your
797 request due to maintenance downtime or capacity
798 problems. Please try again later.</p>
799 """.strip()
800
801
803 """ 504 Gateway Timeout (RFC 2616) """
804 status, reason = 504, "Gateway Timeout"
805 message = """
806 <p>The proxy server did not receive a timely response
807 from the upstream server.</p>
808 """.strip()
809
810
812 """ 505 HTTP Version Not Supported (RFC 2616) """
813 status, reason = 505, "HTTP Version Not Supported"
814 message = """
815 <p>The HTTP version used in the request is not suppored.</p>
816 """.strip()
817
818
820 """ 506 Variant Also Negotiates (RFC 2295) """
821 status, reason = 506, "Variant Also Negotiates"
822 message = """
823 <p>A variant for the requested resource %(url)s is itself a negotiable
824 resource. This indicates a configuration error.</p>
825 """.strip()
826
827
829 """ 507 Insufficient Storage (RFC 2518) """
830 status, reason = 507, "Insufficient Storage"
831 message = """
832 <p>The method could not be performed on the resource
833 because the server is unable to store the
834 representation needed to successfully complete the
835 request. There is insufficient free space left in
836 your storage allocation.</p>
837 """.strip()
838
839
840 -class NotExtended(HTTPResponse):
841 """ 510 Not Extended (RFC 2774) """
842 status, reason = 510, "Not Extended"
843 message = """
844 <p>A mandatory extension policy in the request is not
845 accepted by the server for this resource.</p>
846 """.strip()
847
848
850 """
851 Compute the mapping status code -> status class
852
853 :Parameters:
854 - `space`: Namespace to inspect
855
856 :Types:
857 - `space`: ``dict``
858
859 :return: The mapping (``{status: HTTPResponse, ...}``)
860 :rtype: ``dict``
861 """
862 def _issubclass(inner, outer):
863 """ Determine subclassness """
864 try:
865 return issubclass(inner, outer)
866 except TypeError:
867 return False
868 return dict((cls.status, cls) for cls in space.values()
869 if _issubclass(cls, HTTPResponse) and cls.status)
870 classes = classes(globals())
871
872
874 """
875 Compute the mapping status code -> reason phrase
876
877 :return: The mapping (``{status: 'reason', ...}``)
878 :rtype: ``dict``
879 """
880 return dict((status, cls.reason)
881 for status, cls in classes.iteritems())
882 reasons = reasons()
883