The XML-RPC interface has a compatibility shim for binaries as historically Odoo has returned "binary" data as base64 strings. To avoid breakages during the Python 3 transition, the shim was introduced to decode the output binary data (under the assumption that it'd be ASCII-compatible). In the case where the data is *not* ascii-compatible, however, it can generate invalid XML documents: "C0" control codes (with the exception of tab, LF, and CR) are not valid in XML 1.0 (which XML-RPC is an application of), however they're perfectly valid string characters and the standard library's marshaller does not check for them, embedding them directly in the output document and breaking the client's decoding. Work around the issue by replacing such binary data with an empty string. While at it, move the bytes shim to the customized marshaller, this way everything's at the same place and it's not necessary to waste time trying to understand why the marshaller is just not calling what it's supposed to call. Fixes #61919 closes odoo/odoo#75973 Forward-port-of: #75952 Forward-port-of: #74699 X-original-commit: 1a0b3f7 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
113 lines
4.4 KiB
Python
113 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
"""
|
|
|
|
WSGI stack, common code.
|
|
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
import threading
|
|
import traceback
|
|
|
|
from xmlrpc import client as xmlrpclib
|
|
|
|
import werkzeug.exceptions
|
|
import werkzeug.wrappers
|
|
import werkzeug.serving
|
|
|
|
import odoo
|
|
from odoo.tools import config
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# XML-RPC fault codes. Some care must be taken when changing these: the
|
|
# constants are also defined client-side and must remain in sync.
|
|
# User code must use the exceptions defined in ``odoo.exceptions`` (not
|
|
# create directly ``xmlrpclib.Fault`` objects).
|
|
RPC_FAULT_CODE_CLIENT_ERROR = 1 # indistinguishable from app. error.
|
|
RPC_FAULT_CODE_APPLICATION_ERROR = 1
|
|
RPC_FAULT_CODE_WARNING = 2
|
|
RPC_FAULT_CODE_ACCESS_DENIED = 3
|
|
RPC_FAULT_CODE_ACCESS_ERROR = 4
|
|
|
|
def xmlrpc_handle_exception_int(e):
|
|
if isinstance(e, odoo.exceptions.RedirectWarning):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
|
elif isinstance(e, odoo.exceptions.AccessError):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e))
|
|
elif isinstance(e, odoo.exceptions.AccessDenied):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e))
|
|
elif isinstance(e, odoo.exceptions.UserError):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
|
else:
|
|
info = sys.exc_info()
|
|
# Which one is the best ?
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
#formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
|
|
|
return xmlrpclib.dumps(fault, allow_none=None)
|
|
|
|
def xmlrpc_handle_exception_string(e):
|
|
if isinstance(e, odoo.exceptions.RedirectWarning):
|
|
fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.MissingError):
|
|
fault = xmlrpclib.Fault('warning -- MissingError\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.AccessError):
|
|
fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.AccessDenied):
|
|
fault = xmlrpclib.Fault('AccessDenied', str(e))
|
|
elif isinstance(e, odoo.exceptions.UserError):
|
|
fault = xmlrpclib.Fault('warning -- UserError\n\n' + str(e), '')
|
|
#InternalError
|
|
else:
|
|
info = sys.exc_info()
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
fault = xmlrpclib.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
|
|
|
return xmlrpclib.dumps(fault, allow_none=None, encoding=None)
|
|
|
|
def application_unproxied(environ, start_response):
|
|
""" WSGI entry point."""
|
|
# cleanup db/uid trackers - they're set at HTTP dispatch in
|
|
# web.session.OpenERPSession.send() and at RPC dispatch in
|
|
# odoo.service.web_services.objects_proxy.dispatch().
|
|
# /!\ The cleanup cannot be done at the end of this `application`
|
|
# method because werkzeug still produces relevant logging afterwards
|
|
if hasattr(threading.current_thread(), 'uid'):
|
|
del threading.current_thread().uid
|
|
if hasattr(threading.current_thread(), 'dbname'):
|
|
del threading.current_thread().dbname
|
|
if hasattr(threading.current_thread(), 'url'):
|
|
del threading.current_thread().url
|
|
|
|
result = odoo.http.root(environ, start_response)
|
|
if result is not None:
|
|
return result
|
|
|
|
# We never returned from the loop.
|
|
return werkzeug.exceptions.NotFound("No handler found.\n")(environ, start_response)
|
|
|
|
try:
|
|
# werkzeug >= 0.15
|
|
from werkzeug.middleware.proxy_fix import ProxyFix as ProxyFix_
|
|
# 0.15 also supports port and prefix, but 0.14 only forwarded for, proto
|
|
# and host so replicate that
|
|
ProxyFix = lambda app: ProxyFix_(app, x_for=1, x_proto=1, x_host=1)
|
|
except ImportError:
|
|
# werkzeug < 0.15
|
|
from werkzeug.contrib.fixers import ProxyFix
|
|
|
|
def application(environ, start_response):
|
|
# FIXME: is checking for the presence of HTTP_X_FORWARDED_HOST really useful?
|
|
# we're ignoring the user configuration, and that means we won't
|
|
# support the standardised Forwarded header once werkzeug supports
|
|
# it
|
|
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
|
|
return ProxyFix(application_unproxied)(environ, start_response)
|
|
else:
|
|
return application_unproxied(environ, start_response)
|