mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-03 01:43:37 +00:00
0d9fbc1ad7
This version of SickBeard uses both TVDB and TVRage to search and gather it's series data from allowing you to now have access to and download shows that you couldn't before because of being locked into only what TheTVDB had to offer. Also this edition is based off the code we used in our XEM editon so it does come with scene numbering support as well as all the other features our XEM edition has to offer. Please before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present, we HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk! Enjoy!
396 lines
14 KiB
Python
396 lines
14 KiB
Python
"""Error classes for CherryPy."""
|
|
|
|
from cgi import escape as _escape
|
|
from sys import exc_info as _exc_info
|
|
from traceback import format_exception as _format_exception
|
|
from urlparse import urljoin as _urljoin
|
|
from cherrypy.lib import httputil as _httputil
|
|
|
|
|
|
class CherryPyException(Exception):
|
|
pass
|
|
|
|
|
|
class TimeoutError(CherryPyException):
|
|
"""Exception raised when Response.timed_out is detected."""
|
|
pass
|
|
|
|
|
|
class InternalRedirect(CherryPyException):
|
|
"""Exception raised to switch to the handler for a different URL.
|
|
|
|
Any request.params must be supplied in a query string.
|
|
"""
|
|
|
|
def __init__(self, path, query_string=""):
|
|
import cherrypy
|
|
self.request = cherrypy.serving.request
|
|
|
|
self.query_string = query_string
|
|
if "?" in path:
|
|
# Separate any params included in the path
|
|
path, self.query_string = path.split("?", 1)
|
|
|
|
# Note that urljoin will "do the right thing" whether url is:
|
|
# 1. a URL relative to root (e.g. "/dummy")
|
|
# 2. a URL relative to the current path
|
|
# Note that any query string will be discarded.
|
|
path = _urljoin(self.request.path_info, path)
|
|
|
|
# Set a 'path' member attribute so that code which traps this
|
|
# error can have access to it.
|
|
self.path = path
|
|
|
|
CherryPyException.__init__(self, path, self.query_string)
|
|
|
|
|
|
class HTTPRedirect(CherryPyException):
|
|
"""Exception raised when the request should be redirected.
|
|
|
|
The new URL must be passed as the first argument to the Exception,
|
|
e.g., HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL is
|
|
absolute, it will be used as-is. If it is relative, it is assumed
|
|
to be relative to the current cherrypy.request.path_info.
|
|
"""
|
|
|
|
def __init__(self, urls, status=None):
|
|
import cherrypy
|
|
request = cherrypy.serving.request
|
|
|
|
if isinstance(urls, basestring):
|
|
urls = [urls]
|
|
|
|
abs_urls = []
|
|
for url in urls:
|
|
# Note that urljoin will "do the right thing" whether url is:
|
|
# 1. a complete URL with host (e.g. "http://www.example.com/test")
|
|
# 2. a URL relative to root (e.g. "/dummy")
|
|
# 3. a URL relative to the current path
|
|
# Note that any query string in cherrypy.request is discarded.
|
|
url = _urljoin(cherrypy.url(), url)
|
|
abs_urls.append(url)
|
|
self.urls = abs_urls
|
|
|
|
# RFC 2616 indicates a 301 response code fits our goal; however,
|
|
# browser support for 301 is quite messy. Do 302/303 instead. See
|
|
# http://www.alanflavell.org.uk/www/post-redirect.html
|
|
if status is None:
|
|
if request.protocol >= (1, 1):
|
|
status = 303
|
|
else:
|
|
status = 302
|
|
else:
|
|
status = int(status)
|
|
if status < 300 or status > 399:
|
|
raise ValueError("status must be between 300 and 399.")
|
|
|
|
self.status = status
|
|
CherryPyException.__init__(self, abs_urls, status)
|
|
|
|
def set_response(self):
|
|
"""Modify cherrypy.response status, headers, and body to represent self.
|
|
|
|
CherryPy uses this internally, but you can also use it to create an
|
|
HTTPRedirect object and set its output without *raising* the exception.
|
|
"""
|
|
import cherrypy
|
|
response = cherrypy.serving.response
|
|
response.status = status = self.status
|
|
|
|
if status in (300, 301, 302, 303, 307):
|
|
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
|
# "The ... URI SHOULD be given by the Location field
|
|
# in the response."
|
|
response.headers['Location'] = self.urls[0]
|
|
|
|
# "Unless the request method was HEAD, the entity of the response
|
|
# SHOULD contain a short hypertext note with a hyperlink to the
|
|
# new URI(s)."
|
|
msg = {300: "This resource can be found at <a href='%s'>%s</a>.",
|
|
301: "This resource has permanently moved to <a href='%s'>%s</a>.",
|
|
302: "This resource resides temporarily at <a href='%s'>%s</a>.",
|
|
303: "This resource can be found at <a href='%s'>%s</a>.",
|
|
307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
|
|
}[status]
|
|
msgs = [msg % (u, u) for u in self.urls]
|
|
response.body = "<br />\n".join(msgs)
|
|
# Previous code may have set C-L, so we have to reset it
|
|
# (allow finalize to set it).
|
|
response.headers.pop('Content-Length', None)
|
|
elif status == 304:
|
|
# Not Modified.
|
|
# "The response MUST include the following header fields:
|
|
# Date, unless its omission is required by section 14.18.1"
|
|
# The "Date" header should have been set in Response.__init__
|
|
|
|
# "...the response SHOULD NOT include other entity-headers."
|
|
for key in ('Allow', 'Content-Encoding', 'Content-Language',
|
|
'Content-Length', 'Content-Location', 'Content-MD5',
|
|
'Content-Range', 'Content-Type', 'Expires',
|
|
'Last-Modified'):
|
|
if key in response.headers:
|
|
del response.headers[key]
|
|
|
|
# "The 304 response MUST NOT contain a message-body."
|
|
response.body = None
|
|
# Previous code may have set C-L, so we have to reset it.
|
|
response.headers.pop('Content-Length', None)
|
|
elif status == 305:
|
|
# Use Proxy.
|
|
# self.urls[0] should be the URI of the proxy.
|
|
response.headers['Location'] = self.urls[0]
|
|
response.body = None
|
|
# Previous code may have set C-L, so we have to reset it.
|
|
response.headers.pop('Content-Length', None)
|
|
else:
|
|
raise ValueError("The %s status code is unknown." % status)
|
|
|
|
def __call__(self):
|
|
"""Use this exception as a request.handler (raise self)."""
|
|
raise self
|
|
|
|
|
|
def clean_headers(status):
|
|
"""Remove any headers which should not apply to an error response."""
|
|
import cherrypy
|
|
|
|
response = cherrypy.serving.response
|
|
|
|
# Remove headers which applied to the original content,
|
|
# but do not apply to the error page.
|
|
respheaders = response.headers
|
|
for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
|
|
"Vary", "Content-Encoding", "Content-Length", "Expires",
|
|
"Content-Location", "Content-MD5", "Last-Modified"]:
|
|
if key in respheaders:
|
|
del respheaders[key]
|
|
|
|
if status != 416:
|
|
# A server sending a response with status code 416 (Requested
|
|
# range not satisfiable) SHOULD include a Content-Range field
|
|
# with a byte-range-resp-spec of "*". The instance-length
|
|
# specifies the current length of the selected resource.
|
|
# A response with status code 206 (Partial Content) MUST NOT
|
|
# include a Content-Range field with a byte-range- resp-spec of "*".
|
|
if "Content-Range" in respheaders:
|
|
del respheaders["Content-Range"]
|
|
|
|
|
|
class HTTPError(CherryPyException):
|
|
""" Exception used to return an HTTP error code (4xx-5xx) to the client.
|
|
This exception will automatically set the response status and body.
|
|
|
|
A custom message (a long description to display in the browser)
|
|
can be provided in place of the default.
|
|
"""
|
|
|
|
def __init__(self, status=500, message=None):
|
|
self.status = status
|
|
try:
|
|
self.code, self.reason, defaultmsg = _httputil.valid_status(status)
|
|
except ValueError, x:
|
|
raise self.__class__(500, x.args[0])
|
|
|
|
if self.code < 400 or self.code > 599:
|
|
raise ValueError("status must be between 400 and 599.")
|
|
|
|
# See http://www.python.org/dev/peps/pep-0352/
|
|
# self.message = message
|
|
self._message = message or defaultmsg
|
|
CherryPyException.__init__(self, status, message)
|
|
|
|
def set_response(self):
|
|
"""Modify cherrypy.response status, headers, and body to represent self.
|
|
|
|
CherryPy uses this internally, but you can also use it to create an
|
|
HTTPError object and set its output without *raising* the exception.
|
|
"""
|
|
import cherrypy
|
|
|
|
response = cherrypy.serving.response
|
|
|
|
clean_headers(self.code)
|
|
|
|
# In all cases, finalize will be called after this method,
|
|
# so don't bother cleaning up response values here.
|
|
response.status = self.status
|
|
tb = None
|
|
if cherrypy.serving.request.show_tracebacks:
|
|
tb = format_exc()
|
|
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
|
response.headers.pop('Content-Length', None)
|
|
|
|
content = self.get_error_page(self.status, traceback=tb,
|
|
message=self._message)
|
|
response.body = content
|
|
|
|
_be_ie_unfriendly(self.code)
|
|
|
|
def get_error_page(self, *args, **kwargs):
|
|
return get_error_page(*args, **kwargs)
|
|
|
|
def __call__(self):
|
|
"""Use this exception as a request.handler (raise self)."""
|
|
raise self
|
|
|
|
|
|
class NotFound(HTTPError):
|
|
"""Exception raised when a URL could not be mapped to any handler (404)."""
|
|
|
|
def __init__(self, path=None):
|
|
if path is None:
|
|
import cherrypy
|
|
request = cherrypy.serving.request
|
|
path = request.script_name + request.path_info
|
|
self.args = (path,)
|
|
HTTPError.__init__(self, 404, "The path '%s' was not found." % path)
|
|
|
|
|
|
_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
|
|
<title>%(status)s</title>
|
|
<style type="text/css">
|
|
#powered_by {
|
|
margin-top: 20px;
|
|
border-top: 2px solid black;
|
|
font-style: italic;
|
|
}
|
|
|
|
#traceback {
|
|
color: red;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>%(status)s</h2>
|
|
<p>%(message)s</p>
|
|
<pre id="traceback">%(traceback)s</pre>
|
|
<div id="powered_by">
|
|
<span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
def get_error_page(status, **kwargs):
|
|
"""Return an HTML page, containing a pretty error response.
|
|
|
|
status should be an int or a str.
|
|
kwargs will be interpolated into the page template.
|
|
"""
|
|
import cherrypy
|
|
|
|
try:
|
|
code, reason, message = _httputil.valid_status(status)
|
|
except ValueError, x:
|
|
raise cherrypy.HTTPError(500, x.args[0])
|
|
|
|
# We can't use setdefault here, because some
|
|
# callers send None for kwarg values.
|
|
if kwargs.get('status') is None:
|
|
kwargs['status'] = "%s %s" % (code, reason)
|
|
if kwargs.get('message') is None:
|
|
kwargs['message'] = message
|
|
if kwargs.get('traceback') is None:
|
|
kwargs['traceback'] = ''
|
|
if kwargs.get('version') is None:
|
|
kwargs['version'] = cherrypy.__version__
|
|
|
|
for k, v in kwargs.iteritems():
|
|
if v is None:
|
|
kwargs[k] = ""
|
|
else:
|
|
kwargs[k] = _escape(kwargs[k])
|
|
|
|
# Use a custom template or callable for the error page?
|
|
pages = cherrypy.serving.request.error_page
|
|
error_page = pages.get(code) or pages.get('default')
|
|
if error_page:
|
|
try:
|
|
if callable(error_page):
|
|
return error_page(**kwargs)
|
|
else:
|
|
return open(error_page, 'rb').read() % kwargs
|
|
except:
|
|
e = _format_exception(*_exc_info())[-1]
|
|
m = kwargs['message']
|
|
if m:
|
|
m += "<br />"
|
|
m += "In addition, the custom error page failed:\n<br />%s" % e
|
|
kwargs['message'] = m
|
|
|
|
return _HTTPErrorTemplate % kwargs
|
|
|
|
|
|
_ie_friendly_error_sizes = {
|
|
400: 512, 403: 256, 404: 512, 405: 256,
|
|
406: 512, 408: 512, 409: 512, 410: 256,
|
|
500: 512, 501: 512, 505: 512,
|
|
}
|
|
|
|
|
|
def _be_ie_unfriendly(status):
|
|
import cherrypy
|
|
response = cherrypy.serving.response
|
|
|
|
# For some statuses, Internet Explorer 5+ shows "friendly error
|
|
# messages" instead of our response.body if the body is smaller
|
|
# than a given size. Fix this by returning a body over that size
|
|
# (by adding whitespace).
|
|
# See http://support.microsoft.com/kb/q218155/
|
|
s = _ie_friendly_error_sizes.get(status, 0)
|
|
if s:
|
|
s += 1
|
|
# Since we are issuing an HTTP error status, we assume that
|
|
# the entity is short, and we should just collapse it.
|
|
content = response.collapse_body()
|
|
l = len(content)
|
|
if l and l < s:
|
|
# IN ADDITION: the response must be written to IE
|
|
# in one chunk or it will still get replaced! Bah.
|
|
content = content + (" " * (s - l))
|
|
response.body = content
|
|
response.headers[u'Content-Length'] = str(len(content))
|
|
|
|
|
|
def format_exc(exc=None):
|
|
"""Return exc (or sys.exc_info if None), formatted."""
|
|
if exc is None:
|
|
exc = _exc_info()
|
|
if exc == (None, None, None):
|
|
return ""
|
|
import traceback
|
|
return "".join(traceback.format_exception(*exc))
|
|
|
|
def bare_error(extrabody=None):
|
|
"""Produce status, headers, body for a critical error.
|
|
|
|
Returns a triple without calling any other questionable functions,
|
|
so it should be as error-free as possible. Call it from an HTTP server
|
|
if you get errors outside of the request.
|
|
|
|
If extrabody is None, a friendly but rather unhelpful error message
|
|
is set in the body. If extrabody is a string, it will be appended
|
|
as-is to the body.
|
|
"""
|
|
|
|
# The whole point of this function is to be a last line-of-defense
|
|
# in handling errors. That is, it must not raise any errors itself;
|
|
# it cannot be allowed to fail. Therefore, don't add to it!
|
|
# In particular, don't call any other CP functions.
|
|
|
|
body = "Unrecoverable error in the server."
|
|
if extrabody is not None:
|
|
if not isinstance(extrabody, str):
|
|
extrabody = extrabody.encode('utf-8')
|
|
body += "\n" + extrabody
|
|
|
|
return ("500 Internal Server Error",
|
|
[('Content-Type', 'text/plain'),
|
|
('Content-Length', str(len(body)))],
|
|
[body])
|
|
|
|
|