Files
odoo_source/addons/website/models/ir_http.py
T
Olivier Dony a7aa853737 [FIX] website: exception handling: let redirects pass untouched
The website module normally tries to render user-friendly
error pages for any error occurring during the processing
of a website-enabled request.
This happens even for werkzeug's HTTPExceptions that have
their own response, because we want the website layout to
be applied on top of the error page.

One special sort of HTTPException should be preserved
without rewriting: the manually-crafted ones generated
with abort(), and usually wrapping a redirect response,
which bears no alteration.
2014-06-27 13:04:31 +02:00

194 lines
7.6 KiB
Python

# -*- coding: utf-8 -*-
import logging
import re
import traceback
import werkzeug
import werkzeug.routing
import openerp
from openerp.addons.base import ir
from openerp.addons.base.ir import ir_qweb
from openerp.addons.website.models.website import slug
from openerp.http import request
from openerp.osv import orm
logger = logging.getLogger(__name__)
class RequestUID(object):
def __init__(self, **kw):
self.__dict__.update(kw)
class ir_http(orm.AbstractModel):
_inherit = 'ir.http'
rerouting_limit = 10
def _get_converters(self):
return dict(
super(ir_http, self)._get_converters(),
model=ModelConverter,
page=PageConverter,
)
def _dispatch(self):
first_pass = not hasattr(request, 'website')
request.website = None
func = None
try:
func, arguments = self._find_handler()
request.website_enabled = func.routing.get('website', False)
except werkzeug.exceptions.NotFound:
# either we have a language prefixed route, either a real 404
# in all cases, website processes them
request.website_enabled = True
if request.website_enabled:
if func:
self._authenticate(func.routing['auth'])
else:
self._auth_method_public()
request.website = request.registry['website'].get_current_website(request.cr, request.uid, context=request.context)
if first_pass:
request.lang = request.website.default_lang_code
request.context['lang'] = request.lang
request.website.preprocess_request(request)
if not func:
path = request.httprequest.path.split('/')
langs = [lg[0] for lg in request.website.get_languages()]
if path[1] in langs:
request.lang = request.context['lang'] = path.pop(1)
path = '/'.join(path) or '/'
return self.reroute(path)
return super(ir_http, self)._dispatch()
def reroute(self, path):
if not hasattr(request, 'rerouting'):
request.rerouting = [request.httprequest.path]
if path in request.rerouting:
raise Exception("Rerouting loop is forbidden")
request.rerouting.append(path)
if len(request.rerouting) > self.rerouting_limit:
raise Exception("Rerouting limit exceeded")
request.httprequest.environ['PATH_INFO'] = path
# void werkzeug cached_property. TODO: find a proper way to do this
for key in ('path', 'full_path', 'url', 'base_url'):
request.httprequest.__dict__.pop(key, None)
return self._dispatch()
def _postprocess_args(self, arguments):
if hasattr(request, 'rerouting'):
url = request.rerouting[0]
else:
url = request.httprequest.url
original_url = url
for arg in arguments.itervalues():
if isinstance(arg, orm.browse_record) and isinstance(arg._uid, RequestUID):
placeholder = arg._uid
arg._uid = request.uid
try:
good_slug = slug(arg)
if str(arg.id) != placeholder.value and placeholder.value != good_slug:
# TODO: properly recompose the url instead of using replace()
url = url.replace(placeholder.value, good_slug)
except KeyError, e:
return self._handle_exception(e, code=404)
if url != original_url:
werkzeug.exceptions.abort(werkzeug.utils.redirect(url))
def _handle_exception(self, exception, code=500):
is_website_request = bool(getattr(request, 'website_enabled', False) and request.website)
if not is_website_request:
# Don't touch non website requests exception handling
return super(ir_http, self)._handle_exception(exception)
else:
try:
response = super(ir_http, self)._handle_exception(exception)
if isinstance(response, Exception):
exception = response
else:
# if parent excplicitely returns a plain response, then we don't touch it
return response
except Exception, e:
exception = e
values = dict(
exception=exception,
traceback=traceback.format_exc(exception),
)
code = getattr(exception, 'code', code)
if isinstance(exception, openerp.exceptions.AccessError):
code = 403
if isinstance(exception, ir_qweb.QWebException):
values.update(qweb_exception=exception)
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
code = 403
if isinstance(exception, werkzeug.exceptions.HTTPException) and code is None:
# Hand-crafted HTTPException likely coming from abort(),
# usually for a redirect response -> return it directly
return exception
if code == 500:
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
if 'qweb_exception' in values:
view = request.registry.get("ir.ui.view")
views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
to_reset = [v for v in views if v.model_data_id.noupdate is True]
values['views'] = to_reset
elif code == 403:
logger.warn("403 Forbidden:\n\n%s", values['traceback'])
values.update(
status_message=werkzeug.http.HTTP_STATUS_CODES[code],
status_code=code,
)
if not request.uid:
self._auth_method_public()
try:
html = request.website._render('website.%s' % code, values)
except Exception:
html = request.website._render('website.http_error', values)
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False):
super(ModelConverter, self).__init__(url_map, model)
self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)'
def to_url(self, value):
return slug(value)
def to_python(self, value):
m = re.match(self.regex, value)
_uid = RequestUID(value=value, match=m, converter=self)
return request.registry[self.model].browse(
request.cr, _uid, int(m.group(1)), context=request.context)
def generate(self, cr, uid, query=None, context=None):
return request.registry[self.model].name_search(
cr, uid, name=query or '', context=context)
class PageConverter(werkzeug.routing.PathConverter):
""" Only point of this converter is to bundle pages enumeration logic
Sads got: no way to get the view's human-readable name even if one exists
"""
def generate(self, cr, uid, query=None, context=None):
View = request.registry['ir.ui.view']
views = View.search_read(
cr, uid, [['page', '=', True]],
fields=[], order='name', context=context)
xids = View.get_external_id(
cr, uid, [view['id'] for view in views], context=context)
for view in views:
xid = xids[view['id']]
if xid and (not query or query.lower() in xid.lower()):
yield xid